Python Regex Examples: match, search, groups and sub
Python regex examples you can run: match vs search, groups, named groups, findall, sub with a function, flags, escaping, plus the gotchas that cause bugs.
Python's re module covers almost everything you need with a handful of functions. Use re.search() to find a pattern anywhere in a string, re.match() to check the start of a string, re.fullmatch() to check the whole string, re.findall() to pull out every match, and re.sub() to replace. Write patterns as raw strings (r"...").
Below are the patterns people actually reach for, each short enough to paste into a file. The ones with a Run button execute for real and show their output. The reference is the official re module documentation and the Regular expression HOWTO.
Always write patterns as raw strings#
Regex uses the backslash, and so do Python string literals. That collision is the source of most first-day bugs. The docs' fix is raw string notation: in a string prefixed with r, backslashes aren't handled specially, so r"\n" is two characters (\ and n) while "\n" is one newline.
The worst case is \b. In a normal string it's a backspace character, so this pattern silently fails:
import re
print(re.search('\bclass\b', 'no class at all')) # None: \b became backspace
print(re.search(r'\bclass\b', 'no class at all')) # matches 'class'
Invalid escape sequences in normal string literals also raise a SyntaxWarning now, and the docs say that will become a SyntaxError. Raw strings avoid both problems.
match vs search vs fullmatch#
These three trip people up more than anything else, because the names don't mean what they do in many other languages.
| Function | Where it looks | Returns |
|---|---|---|
re.search() |
First match anywhere in the string | Match or None |
re.match() |
Only at the beginning of the string | Match or None |
re.fullmatch() |
The whole string must match | Match or None |
Run this to see the difference:
import re
text = "From Here to Eternity"
print(re.match(r"Here", text)) # None: text doesn't start with Here
print(re.search(r"Here", text)) # finds it in the middle
print(re.match(r"From", text)) # works at the start
print(re.fullmatch(r"\w+", "Python3")) # whole string is word characters
print(re.fullmatch(r"\d+", "123abc")) # None: trailing letters
Two details from the docs. Even in MULTILINE mode, match() only matches at the start of the string, not at the start of each line. And a None result is different from a zero-length match, so test with is not None if your pattern can match an empty string.
prefixmatch() in Python 3.15#
Python 3.15 added re.prefixmatch() as a second name for match(). It does the same thing, but the name says what it does. The docs soft-deprecate match() and say the old name isn't going away. Their guidance: code that supports older Python versions should keep using match(), and new code can prefer prefixmatch(). I'd keep match() unless you know your minimum version is 3.15.
Groups and named groups#
Parentheses capture part of a match. group(0) is the whole match, group(1) is the first set of parentheses, and groups() returns all of them as a tuple.
Named groups use the Python-specific (?P<name>...) syntax. They still get numbers, so you can fetch them either way, and groupdict() gives you a dictionary.
import re
m = re.search(r"(?P<first>\w+) (?P<last>\w+)", "Jane Doe")
print(m.group("first"), m.group(2))
print(m.groupdict())
# Non-capturing group: repeats without adding a numbered group
print(re.search("([abc])+", "abc").groups())
print(re.search("(?:[abc])+", "abc").groups())
# Backreference by name: doubled words
p = re.compile(r"\b(?P<word>\w+)\s+(?P=word)\b")
print(p.search("Paris in the the spring").group())
The first group example shows a quirk: a repeated capturing group only keeps the last repetition, so ([abc])+ against abc gives ('c',). Use (?:...) when you need grouping for structure but don't want the contents. The HOWTO also notes there's no performance difference between capturing and non-capturing groups, so use it for clarity and stable group numbers, not speed.
I'd use named groups as soon as a pattern has more than two groups. m.group('zonem') is easier to read six months later than m.group(9).
findall and finditer for multiple matches#
re.findall() returns every non-overlapping match, scanned left to right. What it returns depends on how many capturing groups the pattern has:
- No groups: a list of strings, one per whole match.
- Exactly one group: a list of strings for that group.
- Two or more groups: a list of tuples.
Non-capturing groups don't change the shape. When you need positions or named groups, use re.finditer(), which yields Match objects.
import re
text = "set width=20 and height=10"
print(re.findall(r"\b[a-z]+\b", text))
print(re.findall(r"(\w+)=(\d+)", text))
for m in re.finditer(r"(?P<key>\w+)=(?P<val>\d+)", text):
print(m.start(), m.group("key"), int(m.group("val")))
print(re.split(r"\W+", "Words, words, words."))
print(re.split(r"(\W+)", "Words, words, words."))
The last two lines show re.split(). With capturing parentheses in the separator, the separators come back in the list too. Both results end with an empty string because the text ends with a separator.
sub with a function, backreferences and flags#
re.sub(pattern, repl, string) replaces every non-overlapping match. The replacement can be a string or a function.
In a string replacement, \1 refers to group 1, \g<name> refers to a named group, and \g<0> is the whole match. Prefer \g<2> over \2 when a digit follows, because \20 is read as group 20.
If repl is a function, it's called once per match with a Match object and must return the replacement string. That's the way to do anything computed.
import re
def double(m):
return str(int(m.group()) * 2)
print(re.sub(r"\d+", double, "5 apples and 12 pears"))
# Swap names using named backreferences
print(re.sub(r"(?P<first>\w+) (?P<last>\w+)", r"\g<last>, \g<first>", "Jane Doe"))
# Flags, and subn() to also get the replacement count
print(re.subn(r"\sAND\s", " & ", "Baked Beans And Spam", flags=re.IGNORECASE))
# Literal text: escape it first
print(re.escape("https://www.python.org"))
A few more details:
count=limits the number of replacements. Zero or omitted means replace all.- If the pattern isn't found, the original string comes back unchanged.
re.subn()returns a tuple of(new_string, number_of_subs_made).- Since Python 3.5, unmatched groups in a replacement become an empty string.
Flags#
Flags go in the flags= argument, and you combine them with |. The two the pack demonstrates are re.IGNORECASE (used above) and re.MULTILINE. Without MULTILINE, ^ matches only at the start of the string. With it, ^ also matches right after each newline. \A matches only at the start of the string either way.
import re
print(re.findall(r"^\w+", "one\ntwo\nthree")) # ['one']
print(re.findall(r"^\w+", "one\ntwo\nthree", re.MULTILINE)) # ['one', 'two', 'three']
If you reuse a pattern, compile it once with re.compile(pattern, flags). The docs say that's more efficient when the expression is used several times, though recent patterns are also cached, so a small script doesn't need to worry.
Python regex syntax cheat sheet#
These are the pieces covered in the official docs and used above.
| Syntax | Meaning |
|---|---|
\| |
Alternation: Crow\|Servo matches either whole word |
^ |
Start of string (or after each newline with MULTILINE) |
$ |
End of string, or just before a trailing newline |
\A |
Start of string only |
\z / \Z |
End of string only |
\b / \B |
Word boundary / not a word boundary |
(...) |
Capturing group |
(?:...) |
Non-capturing group |
(?P<name>...) |
Named group |
(?P=name) |
Match the same text as the named group again |
\1 |
Match the same text as group 1 again |
(?=foo) |
Positive lookahead |
\w, \W, \d, \s |
Word, non-word, digit, whitespace |
To match a metacharacter literally, put a backslash before it (\^, \$, \|) or put it in a character class like [|] or [$].
Common mistakes and gotchas#
Forgetting that match() is anchored. If re.match("Here", text) returns None and you expected a hit, you wanted search().
Using $ and assuming it means the very end. It also matches before a trailing newline. The docs show }$ matching '{block}\n' but not '{block} '. When the whole string must match, use fullmatch() or \z.
Not escaping user input. If the pattern contains text you didn't write, run it through re.escape(). For a list of literal alternatives, '|'.join(map(re.escape, items)) works. The docs add a caveat: don't use re.escape() on the replacement string in sub(). Only backslashes need escaping there.
Mixing str and bytes. Unicode strings and 8-bit strings can't be mixed. A bytes pattern won't match a str, and the replacement must be the same type as the pattern and the string.
Using findall() with groups and expecting whole matches. Add a group and the output changes shape. Wrap pieces you only need for structure in (?:...), or switch to finditer().
Using regex where code would be clearer. The HOWTO is blunt about this: some tasks can be done with a regex but end up very complicated, and Python code may be slower but more understandable. A long, nested pattern is a sign to split the work into steps.
Treating \b as a Python-aware boundary. Inside a character class, \b means backspace, not a word boundary.
What about a regex generator?#
The standard library doesn't include one, and I wouldn't lean on one for anything you have to maintain. Build the pattern in pieces. Use re.compile() with adjacent raw strings, as the imaplib example in the HOWTO does, and give each field a named group. Then test it with finditer() against a few real lines of input, including ones that should fail.
Quick reference checklist#
- Raw string for every pattern:
r"...". - Anywhere in the string:
search(). Start only:match(). Entire string:fullmatch(). - Many matches:
findall()for strings or tuples,finditer()forMatchobjects. - More than two groups: name them with
(?P<name>...). - Computed replacement: pass a function to
sub(). Need the count:subn(). - Literal text in a pattern:
re.escape(). - Flags:
flags=re.IGNORECASE | re.MULTILINE. - Reused pattern:
re.compile()once. - Python 3.15 or newer only:
prefixmatch()is the explicit name formatch().
Quick answers
What is the difference between re.match and re.search?
re.match() only matches at the beginning of the string, while re.search() scans the whole string for the first match. Both return a Match object or None.
How do I get all matches in Python regex?
Use re.findall() for a list of strings (or tuples when the pattern has multiple groups), or re.finditer() to get Match objects with positions and named groups.
How do I name a group in a Python regex?
Use (?P<name>...), then read it with m.group('name') or get all of them with m.groupdict(). You can reuse the same text later in the pattern with (?P=name).
Is re.match deprecated in Python?
In Python 3.15 it's soft deprecated in favor of re.prefixmatch(), which behaves the same. The docs say match() isn't planned for removal, and code supporting older Python versions should keep using it.
Next note