🕯️ Magic Note
Regular expressions date back to the 1950s and the work of mathematician Stephen Kleene. They were introduced to Unix in the 1970s and have since become a standard feature in almost every programming language. Python’s re module implements Perl-compatible regular expressions (PCRE).
Python
# Without raw string (backslashes need escaping)
pattern = “\\d+\\.\\d{2}” # Hard to read
# With raw string (recommended)
pattern = r”\d+\.\d{2}” # Clean and readable
Python
import re
text = “The price is $19.99 and $29.99”
# search(): find first match
match = re.search(r”\$\d+\.\d{2}”, text)
if match:
print(f”First match: {match.group()}”)
# findall(): find all matches (returns list)
all_prices = re.findall(r”\$\d+\.\d{2}”, text)
print(f”All prices: {all_prices}”)
# finditer(): find all matches (returns iterator of match objects)
for match in re.finditer(r”\$\d+\.\d{2}”, text):
print(f”Found: {match.group()} at position {match.start()}-{match.end()}”)
# sub(): replace all matches
redacted = re.sub(r”\$\d+\.\d{2}”, “$XX.XX”, text)
print(f”Redacted: {redacted}”)
🕯️ Magic Note
The re.match() function checks for a match only at the beginning of the string. re.search() scans the entire string. re.fullmatch() requires the entire string to match. Choose the right function for your use case.
| Pattern | Matches |
|---|---|
| “hello” | The literal string “hello” |
| “.” | Any single character (except newline) |
| “^” | Start of string |
| “$” | End of string |
| “*” | 0 or more repetitions of the previous pattern |
| “+” | 1 or more repetitions of the previous pattern |
| “?” | 0 or 1 repetition of the previous pattern |
| “{n}” | Exactly n repetitions |
| “{n,m}” | Between n and m repetitions |
| “|” | OR (matches either pattern) |
| “[]” | Character class (matches any character inside) |
| “[^]” | Negated character class |
| “\d” | Any digit (0-9) |
| “\D” | Any non-digit |
| “\w” | Any word character (a-z, A-Z, 0-9, _) |
| “\W” | Any non-word character |
| “\s” | Any whitespace (space, tab, newline) |
| “\S” | Any non-whitespace |
| “\b” | Word boundary |
| “\B” | Non-word boundary |
Python
import re
text = “The year is 2025, and the temperature is 25.5°C”
# \d matches digits
print(re.findall(r”\d”, text)) # [‘2’, ‘0’, ‘2’, ‘5’, ‘2’, ‘5’, ‘5’]
print(re.findall(r”\d+”, text)) # [‘2025′, ’25’, ‘5’]
# \w matches word characters
print(re.findall(r”\w+”, text)) # [‘The’, ‘year’, ‘is’, ‘2025’, ‘and’, ‘the’, ‘temperature’, ‘is’, ’25’, ‘5’, ‘C’]
# \s matches whitespace
print(re.findall(r”\s+”, text)) # [‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘, ‘ ‘]
# [] character class
print(re.findall(r”[aeiou]”, text)) # [‘e’, ‘e’, ‘i’, ‘a’, ‘e’, ‘e’, ‘e’, ‘i’, ‘u’]
print(re.findall(r”[0-9]+”, text)) # [‘2025′, ’25’, ‘5’]
# Anchors: ^ (start) and $ (end)
print(re.findall(r”^\w+”, text)) # [‘The’] (word at start)
print(re.findall(r”\w+$”, text)) # [‘C’] (word at end)
Python
import re
text = “Colors: red, green, blue, darkred, lightblue”
# {n} – exactly n times
print(re.findall(r”\w{4}”, text)) # [‘blue’, ‘dark’, ‘blue’] (4 letter words)
# {n,m} – between n and m times
print(re.findall(r”\w{3,5}”, text)) # [‘red’, ‘gree’, ‘blue’, ‘dark’, ‘light’, ‘blue’]
# * – 0 or more (greedy)
print(re.findall(r”colou*r”, “color colour colur colr”)) # [‘color’, ‘colour’, ‘colur’, ‘colr’]
# + – 1 or more
print(re.findall(r”\d+”, “a1 b22 c333”)) # [‘1′, ’22’, ‘333’]
# ? – 0 or 1
print(re.findall(r”https?://”, “http:// https:// ftp://”)) # [‘http://’, ‘https://’]
🕯️ Magic Note
Quantifiers are greedy by default. They match as much as possible. Add ? after a quantifier to make it non-greedy (lazy): .*? matches the shortest possible match instead of the longest.
Python
import re
text = “Name: Ali, Age: 25, City: Tehran”
# Single group (extract one part)
match = re.search(r”Age: (\d+)”, text)
if match:
print(f”Age group 1: {match.group(1)}”) # First group
print(f”Full match: {match.group(0)}”) # Entire match
# Multiple groups
match = re.search(r”Name: (\w+), Age: (\d+), City: (\w+)”, text)
if match:
name, age, city = match.groups()
print(f”Name: {name}, Age: {age}, City: {city}”)
# findall with groups returns tuples
data = “Ali:25, Sara:30, Reza:28”
pairs = re.findall(r”(\w+):(\d+)”, data)
print(pairs) # [(‘Ali’, ’25’), (‘Sara’, ’30’), (‘Reza’, ’28’)]
# finditer with groups
for name, age in re.finditer(r”(\w+):(\d+)”, data):
print(f”name: {name.group(1)}, age: {age.group(2)}”)
Python
import re
text = “Order: #12345, Date: 2025-05-10”
pattern = r”Order: #(?P<order_id>\d+), Date: (?P<date>\d{4}-\d{2}-\d{2})”
match = re.search(pattern, text)
if match:
print(f”Order ID: {match.group(‘order_id’)}”)
print(f”Date: {match.group(‘date’)}”)
print(f”All named groups: {match.groupdict()}”)
# {‘order_id’: ‘12345’, ‘date’: ‘2025-05-10’}
🕯️ Magic Note
Named groups are especially useful when your regex has many groups. Instead of remembering positions, you use meaningful names. This makes your code self-documenting.
Python
import re
text = “abc123 DEF!@#”
# Predefined classes
print(re.findall(r”\d”, text)) # [‘1’, ‘2’, ‘3’] (digits)
print(re.findall(r”\w”, text)) # [‘a’,’b’,’c’,’1′,’2′,’3′,’D’,’E’,’F’] (word chars)
print(re.findall(r”\s”, text)) # [‘ ‘, ‘ ‘] (whitespace)
# Custom classes
print(re.findall(r”[aeiou]”, text)) # [‘a’] (vowels)
print(re.findall(r”[A-Z]”, text)) # [‘D’, ‘E’, ‘F’] (uppercase)
print(re.findall(r”[a-z]”, text)) # [‘a’, ‘b’, ‘c’] (lowercase)
# Negated classes [^]
print(re.findall(r”[^a-zA-Z]”, text)) # [‘1′,’2′,’3′,’ ‘,’!’,’@’,’#’]
# Character ranges
print(re.findall(r”[0-9a-f]”, “12af 89gh”)) # [‘1′,’2′,’a’,’f’,’8′,’9′]
Python
import re
text = “What is 2 + 2? Answer: 4.”
# Match literal plus sign (needs escaping)
print(re.findall(r”\+”, text)) # [‘+’]
# Match literal dot (needs escaping)
print(re.findall(r”\.”, text)) # [‘.’]
# Match literal parentheses
print(re.findall(r”\(\)”, “( )”)) # [‘()’]
# Match literal asterisk
print(re.findall(r”\*”, “*”)) # [‘*’]
Python
import re
# Compile once
email_pattern = re.compile(r”[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}”)
# Reuse many times (faster)
if email_pattern.match(“user@example.com”):
print(“Valid email 1”)
if email_pattern.match(“another@test.org”):
print(“Valid email 2”)
# Compiled patterns have the same methods
all_emails = email_pattern.findall(“Contact: user@example.com and admin@test.org”)
print(all_emails) # [‘user@example.com’, ‘admin@test.org’]
Python
import re
# Email addresses
email = r”[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}”
# Phone numbers (US format)
phone = r”\d{3}[-.]?\d{3}[-.]?\d{4}”
# Dates (YYYY-MM-DD)
date = r”\d{4}-\d{2}-\d{2}”
# URL (simple version)
url = r”https?://[a-zA-Z0-9./_-]+”
# IP address (simplified)
ip = r”\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}”
# Hex color code
hex_color = r”#[0-9a-fA-F]{6}”
# US ZIP code
zip_code = r”\d{5}(?:-\d{4})?”
Python
import re
text = “Hello World\nHELLO WORLD”
# re.IGNORECASE (or re.I) – case insensitive
print(re.findall(r”hello”, text, re.IGNORECASE)) # [‘Hello’, ‘HELLO’]
# re.MULTILINE (or re.M) – ^ and $ match line starts/ends
print(re.findall(r”^HELLO”, text, re.MULTILINE)) # [‘HELLO’] (second line)
# re.DOTALL (or re.S) – dot matches newline as well
print(re.findall(r”Hello.*World”, text, re.DOTALL)) # [‘Hello World\nHELLO WORLD’]
# re.VERBOSE (or re.X) – allow comments and whitespace in pattern
pattern = re.compile(r”””
\d{4} # Year
– # Separator
\d{2} # Month
– # Separator
\d{2} # Day
“””, re.VERBOSE)
print(pattern.findall(“2025-05-10”)) # [‘2025-05-10’]
🕯️ Magic Note
The re.VERBOSE flag makes complex regex patterns readable. You can add comments and whitespace. Use it for patterns that are hard to understand at a glance.
Python
import re
text = “I have 10 apples and 20 oranges”
# Positive lookahead (?=…) – match if followed by
print(re.findall(r”\d+(?= apples)”, text)) # [’10’] (numbers before “apples”)
# Negative lookahead (?!…) – match if not followed by
print(re.findall(r”\d+(?! apples)”, text)) # [’20’] (numbers not before “apples”)
# Positive lookbehind (?<=…) – match if preceded by
print(re.findall(r”(?<=apples )\d+”, text)) # [] (numbers after “apples”)
print(re.findall(r”(?<=oranges )\d+”, text)) # []? wait no number after oranges
# Negative lookbehind (?<!…) – match if not preceded by
print(re.findall(r”(?<!apples )\d+”, text)) # [’10’, ’20’]? actually need careful
Python
import re
log_line = ‘2025-05-10 14:30:45,123 ERROR [main] Connection failed: timeout (user=ali, ip=192.168.1.1)’
pattern = re.compile(
r”(?P<date>\d{4}-\d{2}-\d{2})”
r”\s+”
r”(?P<time>\d{2}:\d{2}:\d{2},\d{3})”
r”\s+”
r”(?P<level>\w+)”
r”\s+”
r”\[(?P<component>\w+)\]”
r”\s+”
r”(?P<message>.*?)\s*\(user=(?P<user>\w+),\s*ip=(?P<ip>[\d\.]+)\)”
)
match = pattern.match(log_line)
if match:
print(f”Date: {match.group(‘date’)}”)
print(f”Time: {match.group(‘time’)}”)
print(f”Level: {match.group(‘level’)}”)
print(f”Component: {match.group(‘component’)}”)
print(f”Message: {match.group(‘message’)}”)
print(f”User: {match.group(‘user’)}”)
print(f”IP: {match.group(‘ip’)}”)
- Forgetting to use raw strings (r”…”) causing unintended backslash escapes
- Using greedy quantifiers when non-greedy would be appropriate
- Assuming re.match() searches the entire string (it only checks the start)
- Not escaping special characters that should be literal
- Making regex patterns too complex (break them into smaller steps)
- Using regex for simple string operations where .find() or .replace() would suffice
- Write a regex to find all email addresses in a string.
- What is the difference between re.search() and re.findall()?
- How do you extract the domain part from an email address using groups?
- What does the pattern r”\d{3}-\d{2}-\d{4}” match?
- What is the purpose of re.compile()?
- Write a regex that validates a US phone number format: (123) 456-7890 or 123-456-7890
⚡ Whisper
Regular expressions are a language within a language. At first, they look like line noise. \d+\.\d{2} seems cryptic. But each symbol has meaning. \d is a digit. + means one or more. \. is a literal dot. \d{2} means two digits. Once you learn the symbols, you see patterns everywhere. Emails become \w+@\w+\.\w+. Dates become \d{4}-\d{2}-\d{2}. Phone numbers become patterns of digits and dashes. Regex is not magic. It is a skill. Start simple. Practice. Use online regex testers (regex101.com). Break complex patterns into pieces. Add comments with re.VERBOSE. And remember: regex is powerful, but power invites complexity. For simple string operations, .find() and .replace() are often enough. Use regex when you need flexibility. Use regex when patterns vary. Use regex when you need to extract meaning from chaos. Then the chaos becomes order.