re

A pattern language for text. search finds the first match, findall collects them all, sub replaces, split cuts — and every pattern belongs in a raw string: r'\d+', not '\d+'.

TextPython 3 (all)Live demo
Import
import re
pattern = re.compile(r'\d+')
Workhorses
search, match, fullmatch, findall, finditer, sub, subn, split, compile, escape
Objects
re.Pattern (a compiled pattern) and re.Match (one successful match); no match returns None
Engine
Backtracking matcher in C (Modules/_sre), Perl-style syntax; str patterns are Unicode-aware by default
Errors
re.PatternError (alias re.error) for a bad pattern — with the position of the problem

Demo

Live evaluation
The first place in the text where the pattern matches — or None.
Try:
Inputs
patternstra regular expression
textstrtext to search
Code
import re
m = re.search('\\d{4}-\\d{2}-\\d{2}', 'Invoice due 2026-09-29, paid 2026-10-02')
m.group() if m else None
Result
'2026-09-29'

The code shows each pattern the way Python's repr() writes it, so every backslash appears doubled: '\\d+' in the box is exactly the string you would type as r'\d+'. Two results worth a second look: findall with groups returns the groups, not the whole match, and splitting on whitespace leaves an empty string at each end when the text starts or ends with a separator.

Members

Common patterns

Compile once, reuse many times
A Pattern object has the same methods as the module functions.
import re
DATE = re.compile(r'(\d{4})-(\d{2})-(\d{2})')
for line in lines:
    if m := DATE.search(line):
        year, month, day = m.groups()
Validate a whole string
fullmatch anchors both ends — no ^ and $ needed, and no trailing-newline surprise.
import re
def is_zip(s):
    return re.fullmatch(r'\d{5}(-\d{4})?', s) is not None
Named groups for readable extraction
(?P<name>...) plus groupdict() gives you a dict.
import re
m = re.search(r'(?P<user>[\w.]+)@(?P<host>[\w.-]+)', text)
if m:
    info = m.groupdict()  # {'user': ..., 'host': ...}
Defuse catastrophic backtracking
A quantified group containing a quantifier can try exponentially many splits before failing. Simplify it, or forbid giving characters back (3.11+).
import re
BAD  = re.compile(r'^(a+)+$')     # near-misses like 'aaaa…ab' take exponential time
GOOD = re.compile(r'^a+$')        # same strings, linear
ALSO = re.compile(r'^(?:a+)++$')  # possessive: no backtracking into the group
A readable pattern with VERBOSE
Whitespace is ignored and # starts a comment.
import re
PHONE = re.compile(r'''
    (\d{3})   # area code
    [-.\s]?
    (\d{4})   # number
''', re.VERBOSE)

Examples

1. search returns a Match object
import re re.search(r'\d+', 'abc123def45')
Returns
<re.Match object; span=(3, 6), match='123'>
2. findall returns strings
import re re.findall(r'\d+', 'a1 b22 c333')
Returns
['1', '22', '333']
3. Groups change what findall returns
import re re.findall(r'(\w)(\d+)', 'a1 b22 c333')
Returns
[('a', '1'), ('b', '22'), ('c', '333')]
4. sub with group references
import re re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\3/\2/\1', 'due 2026-09-29')
Returns
'due 29/09/2026'
5. split on several separators
import re re.split(r'[,;]\s*', 'a, b;c, d')
Returns
['a', 'b', 'c', 'd']
6. A compiled pattern
import re re.compile(r'\d+', re.IGNORECASE | re.MULTILINE)
Returns
re.compile('\\d+', re.IGNORECASE|re.MULTILINE)
7. Possessive a++ never gives back (3.11+)
import re (re.match(r'a+a', 'aaaa'), re.match(r'a++a', 'aaaa'))
Returns
(<re.Match object; span=(0, 4), match='aaaa'>, None)
8. A broken pattern says where
import re re.compile(r'(\d+')
Returns
re.PatternError: missing ), unterminated subpattern at position 0

Pitfalls

1. Forgetting the r prefix
In a normal string '\b' is a backspace character, so the regex engine never sees \b (word boundary). The raw string passes the backslash through untouched.
'\bcat\b'
import re
print(re.search('\bcat\b', 'a cat here'))
None
r'\bcat\b'
import re
re.search(r'\bcat\b', 'a cat here')
<re.Match object; span=(2, 5), match='cat'>
2. Using match when you meant search
match only tries position 0. The digits are there — just not at the start.
re.match
import re
re.match(r'\d+', 'order 66') is None
True
re.search
import re
re.search(r'\d+', 'order 66').group()
'66'
3. Calling .group() on None
No match is None, not an empty match. Check before you use it.
no check
import re
re.search(r'\d+', 'none here').group()
AttributeError: 'NoneType' object has no attribute 'group'
check first
import re
m = re.search(r'\d+', 'none here')
m.group() if m else 'no number'
'no number'

When to use

Use it
  • Finding text by shape rather than exact content: dates, IDs, emails, log fields
  • Replacing or splitting with rules str.replace and str.split cannot express
  • Extracting several parts at once with groups
Reach for something else
  • A fixed substring → in, str.find, str.replace, str.startswith (faster and clearer)
  • Splitting on runs of whitespace → str.split() with no argument (drops the empty ends)
  • HTML, JSON, CSV or other nested/quoted formats → a real parser (html.parser, json, csv)

Notes

CPython impl
Lib/re/_parser.py parses the pattern, Lib/re/_compiler.py turns it into opcodes, and the C engine in Modules/_sre runs them with backtracking
Raw strings
Python string escapes are processed before re sees the pattern. r'\d' is backslash + d; '\d' only works by accident (and warns since 3.12), '\b' silently becomes a backspace
Cache
Module-level functions compile and cache patterns (up to 512), so compile() is about clarity and reuse, not a big speed-up
Backtracking
Nested quantifiers such as (a+)+ can take exponential time on a near-miss. Use possessive quantifiers (a++) or atomic groups (?>...) (3.11+), or rewrite the pattern
Unicode
For str patterns \d, \w, \s and case-insensitive matching follow Unicode; pass re.ASCII to restrict them to ASCII

FAQ

Because Python processes backslash escapes in normal strings first. '\b' becomes a backspace, '\1' becomes the character with code 1, and unknown escapes like '\d' raise a SyntaxWarning since Python 3.12. In r'\b\d+' every backslash reaches the regex engine unchanged.