Regular Expressions with Python’s re Module
Learn regular-expression patterns, matching operations, capture groups, substitutions, splitting, flags, and safe limits with Python’s re module.
Intermediate
Regular expressions describe patterns in text. They are useful when a small, well-defined pattern can find, validate, extract, replace, or split text. They are not a substitute for a parser when the input has nested structure, and a complicated pattern can be harder to maintain than ordinary Python code.
Use raw strings for patterns
Regular-expression syntax uses backslashes, and Python string literals use backslashes too. Write most patterns as raw strings such as r"\d+" so Python does not consume the backslashes before the regular-expression engine sees them. Raw strings do not change the regular-expression language; they make the pattern easier to express as a Python string.
Output
Ready to run.
Quick check
Why is a raw string usually the clearest way to write a regular-expression pattern containing backslashes?
import re
print(re.fullmatch(r"\d+", "2048") is not None)
Build patterns from small pieces
Ordinary characters match themselves. Character classes such as [A-Z] match one character from a set; ranges such as [0-9] are explicit, while \d matches a Unicode decimal digit by default for string patterns. The shortcuts \w and \s match word characters and whitespace. Quantifiers say how many repetitions are allowed: * means zero or more, + one or more, ? optional, and {m,n} a bounded count. Anchors such as ^, $, and word boundary \b describe positions rather than characters.
Output
Ready to run.
Quick check
What does the quantifier {2,4} mean when it follows a pattern element?
import re
print(re.fullmatch(r"[A-Z]{2}\d{3}", "AB123") is not None)
Choose the operation that matches your question
Use search() to find the first match anywhere in the input. Use fullmatch() when the entire input must fit a format, such as a product code or a simple field value. Use finditer() when you need match objects and their positions; findall() is convenient when you only need the matched text or captured groups. Compile a reused pattern with re.compile() when that makes the code easier to read or reuses flags.
Python 3.15 API note: the current reference adds prefixmatch() for matching only at the start of a string and soft-deprecates match() in favor of that clearer name. The runnable examples in this archive were executed with Python 3.13.5, where prefixmatch() is not available, so they use search() and fullmatch() instead. Do not copy 3.15-only calls into a program that must support older Python versions.
Output
Ready to run.
Quick check
Which operation should you normally choose when the whole input must match a pattern?
import re
print(re.fullmatch(r"[A-Z]{2}\d{3}", "ZX704") is not None)
Capture meaningful parts of a match
Parentheses capture a part of the match so you can retrieve it. Name a group with (?P<name>...) when the meaning matters more than its numeric position. A non-capturing group (?:...) groups alternatives or repetitions without changing the returned capture structure. Match objects expose group(), groups(), groupdict(), and span(). Backreferences can require a later part of the pattern to repeat an earlier capture; use them only when that relationship is part of the input format.
Output
Ready to run.
Quick check
What does groupdict() return for a successful match with named capturing groups?
import re
match = re.fullmatch(r"(?P<left>\w+):(?P<right>\w+)", "red:blue")
print(match.groupdict())
Find, replace, and split text
finditer() yields one Match object at a time, so you can inspect matched text and positions without building a list of all results first. sub() creates a new string with matched text replaced; named or numbered captures can be reused in a replacement. split() separates text wherever the pattern matches. Capturing parentheses in the split pattern are included in the returned list, so prefer a non-capturing group when you only need structure.
Output
Ready to run.
Quick check
Which function is designed to replace every non-overlapping match by default and return a new string?
import re
print(re.sub(r"\s+", "-", "red blue green"))
Flags, lookarounds, and sensible limits
Flags adjust matching without changing the main pattern: IGNORECASE handles case-insensitive matching, MULTILINE changes line-anchor behavior, DOTALL lets a dot include newlines, and VERBOSE allows readable patterns with comments. Lookahead checks what follows without consuming it; lookbehind checks what precedes it, and Python requires lookbehind patterns to have fixed length. Start with a readable pattern and test normal, boundary, and invalid inputs. Avoid nested ambiguous repetition on long untrusted text, and do not use a regular expression as a replacement for parsing nested syntax.
Output
Ready to run.
Keep these decisions in mind
- Prefer raw strings for patterns containing backslashes.
- Use fullmatch() for whole-input checks, search() for a match anywhere, and finditer() when positions matter.
- Use named groups when captured values have meaning, and keep patterns simpler than the data-processing problem requires.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.