Patterns
A regular expression, or regex, is a pattern that describes text. Python's re module matches patterns against strings. Here s holds 12 characters, one per box, each with its index on top. Every pattern below is tried against this row.
Plain letters match themselves. The engine puts a pointer at index 0 and compares: c is not d, so it moves one box right and tries again. At index 7, d, o and g all match. search() returns this first match.
Classes and repeats
\d means any one digit. Square brackets list your own choices: [a-z] is one lowercase letter, [0-9] one digit. \w is a letter, digit or underscore; \s is a space, tab or newline. Here \d first fits at index 4.
A quantifier says how many: + one or more, * zero or more, ? zero or one, {2,3} two to three. They are greedy. \d+ takes the 4, then the 2, and stops at the space, so the match is 42.
search() stops at the first match. findall() carries on from the end of each match to the end of the string, and returns a list of every match it found: 42 and 7.
Anchors, groups, sub
$ means the end of the string, ^ the start. At index 4, \d+ grabs 42, but a space follows, not the end. So the engine backtracks: it gives back the 2, checks again, fails, and moves on. Only the 7 ends the string.
A bar means or: (dog|cat) tries dog first, then cat. Round brackets make a group, so you can read each part of the match on its own. The match is cat 42; group 1 is cat and group 2 is 42.
re.sub() finds every match and builds a new string with each one replaced. Every digit becomes #, giving cat ## dog #. Strings never change in place, so s itself is still cat 42 dog 7.
Use with care
A repeat inside a repeat, like (\d+)+, can split a run of digits in many ways. When the match fails, the engine tries every split. Each extra digit about doubles the work: 30 digits give over 500 million splits.
A regex is hard to read and easy to get wrong. To find a fixed word, "dog" in s or s.startswith("cat") is simpler. To read HTML, JSON or CSV files, use a real parser: html.parser, json or csv.
Try it in the Python prompt
;; step 1/10 · line 1: s = "cat 42 dog 7" >>> import re >>> s = "cat 42 dog 7" >>> len(s) 12 >>> s[7] 'd'
The r before each pattern makes a raw string, so the backslash in \d reaches the re module unchanged. Every line assumes import re at the top. The moving pointer shows how a backtracking engine searches. CPython's engine adds shortcuts, such as jumping straight to a place where the first character could match, so it may skip some of the tries drawn here; the results are the same.