Regular Expressions HOWTO (re)¶
Regular expressions (called REs, regexes, or regex patterns) are a specialized language embedded inside Python and available through the re module. They allow you to define patterns to search, validate, extract, and substitute complex text sequences.
Metacharacters Reference¶
Metacharacters are characters with special syntax meaning in regex patterns:
| Metacharacter | Meaning | Example | Matches |
|---|---|---|---|
. | Any character except newline (or any with re.DOTALL) | a.c | "abc", "a9c" |
^ | Start of string (or line with re.MULTILINE) | ^Python | "Python 3" |
$ | End of string (or line with re.MULTILINE) | done$ | "well done" |
* | 0 or more occurrences (greedy) | ab* | "a", "ab", "abbb" |
+ | 1 or more occurrences (greedy) | ab+ | "ab", "abbb" (NOT "a") |
? | 0 or 1 occurrence (greedy) | https? | "http", "https" |
{m,n} | Between $m$ and $n$ repetitions | \d{2,4} | "12", "123", "1234" |
[] | Character class (any single character in set) | [aeiou] | Any vowel |
[^] | Negated character class | [^0-9] | Any non-digit |
\| | Alternation (OR) | cat\|dog | "cat", "dog" |
() | Capturing group | (\d{3})-(\d{4}) | Groups area & number |
Predefined Character Classes¶
Always write regex patterns using raw strings (r"...") to prevent Python's string escaping from conflicting with regex escape sequences:
| Class | Equivalent To | Matches |
|---|---|---|
\d | [0-9] | Any decimal digit |
\D | [^0-9] | Any non-digit character |
\s | [ \t\n\r\f\v] | Any whitespace character |
\S | [^ \t\n\r\f\v] | Any non-whitespace character |
\w | [a-zA-Z0-9_] (ASCII) or Unicode alphanumeric | Any word character |
\W | [^\w] | Any non-word character |
\b | Word boundary anchor | Zero-width boundary between \w and \W |
Compiling Patterns with re.compile()¶
When performing repetitive searches inside loops, compile the regex once into a reusable Pattern object for maximum performance:
import re
# Compile once
email_pattern = re.compile(r"^[\w\.-]+@[\w\.-]+\.\w+$")
# Reuse across multiple inputs
emails = ["alice@example.com", "invalid-email@", "bob.dev@company.org"]
for email in emails:
if email_pattern.match(email):
print(f"Valid: {email}")
Core Matching Functions¶
| Function | What it does |
|---|---|
match(pattern, str) | Checks for a match only at the beginning of the string |
search(pattern, str) | Scans through the entire string to find the first match |
findall(pattern, str) | Returns a list of all matching string segments |
finditer(pattern, str) | Yields an iterator of Match objects (memory-efficient for large text) |
sub(pattern, repl, str) | Replaces matching substrings with a replacement |
import re
log_data = "Error at 10:15:32. Warning at 10:18:04. Error at 10:22:11."
# search: finds first match
first_error = re.search(r"Error at (\d{2}:\d{2}:\d{2})", log_data)
if first_error:
print(f"First error timestamp: {first_error.group(1)}")
# findall: extracts all matches as list of strings
all_timestamps = re.findall(r"\d{2}:\d{2}:\d{2}", log_data)
print(f"All timestamps: {all_timestamps}")
Capturing Groups and Named Groups¶
Parentheses define sub-patterns whose matched text can be extracted independently:
Greedy vs Non-Greedy (Lazy) Matching¶
By default, repetition operators (*, +, {m,n}) are greedy: they consume as much text as possible. Append a question mark (?) to make them non-greedy (lazy):
html = "<div>First block</div><div>Second block</div>"
# Greedy: consumes from the first <div> all the way to the LAST </div>!
greedy_match = re.findall(r"<div>.*</div>", html)
print("Greedy count:", len(greedy_match)) # 1 huge match!
# Lazy (Non-greedy): stops at the very first closing </div>
lazy_match = re.findall(r"<div>.*?</div>", html)
print("Lazy count: ", len(lazy_match)) # 2 separate matches!
Readable Multi-Line Regexes with re.VERBOSE (re.X)¶
Complex regular expressions quickly turn into unreadable "line noise". The re.VERBOSE flag ignores whitespace and allows # comments inside your pattern:
import re
phone_regex = re.compile(r"""
^ # Beginning of string
(\+\d{1,3}\s*)? # Optional country code (+1, +44, etc.)
\(?(\d{3})\)? # 3-digit area code with optional parentheses
[\s.-]? # Optional separator (space, dot, dash)
(\d{3}) # 3-digit exchange prefix
[\s.-]? # Optional separator
(\d{4}) # 4-digit subscriber line
$ # End of string
""", re.VERBOSE)
print(bool(phone_regex.match("+1 (415) 555-2671"))) # True
print(bool(phone_regex.match("415.555.2671"))) # True