Skip to content

Anchors

A pattern matches anywhere in the string by default. Anchors pin it to a position: the start, the end, or a word edge.

^ and $: start and end

>>> re.search(r"^cat", "cat sat").group()      # starts with cat
'cat'
>>> re.search(r"^cat", "the cat") is None      # "the cat" doesn't START with cat
True
>>> re.search(r"cat$", "the cat").group()      # ends with cat
'cat'
>>> re.search(r"^cat$", "cat").group()         # the ENTIRE string is "cat"
'cat'
>>> re.search(r"^cat$", "cats") is None
True

All verified. ^cat$ means "the whole string is exactly cat" — that's the shape of validation ("is this input a 5-digit zip?": ^\d{5}$).

Careful: ^ means "start" here, but "not" inside brackets ([^0-9]). Same symbol, two jobs — context tells you which. (And in most flavors ^ also means start-of-line with the multiline flag — see Flags.)

\b: word boundaries

\b matches the edge between a word character and a non-word character — the invisible fence around words:

>>> re.findall(r"\bcat\b", "cat concatenate wildcat")
['cat']    # only the standalone word; "concatenate" untouched

Verified. This is the fix for chapter 1's problem (cat matching inside concatenate). Whenever you mean "the word X," write \bX\b.

>>> re.findall(r"\b\d+\b", "order 42 ships")
['42']

Putting it together: validation

>>> bool(re.search(r"^\d{5}$", "90210"))
True
>>> bool(re.search(r"^\d{5}$", "902101"))
False
>>> bool(re.search(r"^\d{5}$", "abcde"))
False

All verified. Search vs. validation is the key distinction: re.search(r"\d{5}", ...) finds five digits anywhere (extraction); re.search(r"^\d{5}$", ...) requires the whole string to be five digits (validation). Most "my regex doesn't work" complaints are this mix-up.

fullmatch: the explicit version

Python has re.fullmatch — the pattern must match the entire string, no anchors needed:

>>> bool(re.fullmatch(r"\d{5}", "90210"))
True

Verified. Use it when you mean validation; it says what you mean.

Next: Groups