Python Regular Expressions (re) & Enums
A Regular Expression (RegEx) is a powerful domain-specific pattern language used to search, validate, and manipulate text strings.
Python provides the built-in re module. Always use Raw Strings (r"...") for regex patterns so backslashes (like \d) are not misinterpreted as Python escape characters:
| Function | Behavior |
|---|---|
re.search(pattern, text) | Scans entire string and returns the first Match object (or None). |
re.match(pattern, text) | Matches pattern strictly from the very start of the string. |
re.findall(pattern, text) | Returns a list of all matching substrings. |
re.sub(pattern, repl, text) | Replaces all occurrences matching pattern with replacement text. |
import re
log_text = """
2026-08-14 10:15:02 User balaji.dev@example.com logged in from 192.168.1.45.
2026-08-14 10:18:30 User alex_smith99@domain.org failed login from 10.0.0.12.
2026-08-14 10:22:11 User chloe.davis@techcorp.io logged in from 172.16.0.5.
"""
# 1. Extract all Email addresses using findall:
email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
emails = re.findall(email_pattern, log_text)
print("๐ง Extracted Email Addresses:")
for email in emails:
print("โข", email)
# 2. Extract all IPv4 Addresses using findall:
ip_pattern = r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b'
ips = re.findall(ip_pattern, log_text)
print("\n๐ Extracted IP Addresses:", ips)
# 3. Anonymize/Mask emails using re.sub:
masked_log = re.sub(email_pattern, "[CONFIDENTIAL_EMAIL]", log_text)
print("\n๐ก๏ธ Masked Security Log:")
print(masked_log.strip())
\d: Any digit (0-9).\w: Any alphanumeric word character.+: 1 or more occurrences.*: 0 or more occurrences.\b: Word boundary anchor.
Use parentheses (...) to define Capture Groups to extract structured sub-components (such as date parts, phone country codes, or user credentials):
import re
contact_str = "Support Hotline: +91-98765-43210 (Mon-Fri 9AM-6PM)"
# Named Capture Groups (?Ppattern):
phone_pattern = r'+(?Pd{1,3})-(?Pd{3,5})-(?Pd{4,6})'
match = re.search(phone_pattern, contact_str)
if match:
print("Full Matched Number:", match.group(0))
print("Country Code: +", match.group("country_code"))
print("Line Number: ", match.group("line_no"))
print("Named Groups Dict: ", match.groupdict())
(?P allows you to extract fields as dictionary keys via match.groupdict(), making code immune to regex group index changes.
Instead of using raw string constants (like "PENDING" or "COMPLETED") which are prone to typos, Python provides Type-Safe Enumerations (enum.Enum):
from enum import Enum, auto
class OrderStatus(Enum):
PENDING = auto() # Automatically assigns incremental values
PROCESSING = auto()
SHIPPED = auto()
DELIVERED = auto()
CANCELLED = auto()
def update_order(order_id, status: OrderStatus):
if not isinstance(status, OrderStatus):
raise TypeError("status must be a valid OrderStatus enum member!")
print(f"๐ฆ Order #{order_id} status updated to: {status.name} (Value: {status.value})")
update_order(1001, OrderStatus.PROCESSING)
update_order(1001, OrderStatus.DELIVERED)
Enums prevent invalid state bugs, provide instant IDE autocomplete, and allow safe comparison with status is OrderStatus.DELIVERED.
Python 3.9+ introduced the dedicated Dictionary Merge Operator (|) and update operator (|=):
# 1. Extended sequence destructuring:
numbers = [1, 2, 3, 4, 5, 6]
first, *middle, last = numbers
print(f"First: {first} | Middle: {middle} | Last: {last}")
# 2. Modern Dictionary Merging with | (Python 3.9+):
default_config = {"theme": "dark", "font_size": 14, "auto_save": True}
user_overrides = {"font_size": 16, "show_minimap": False}
# Merges both dictionaries, right operand values override left:
active_config = default_config | user_overrides
print("\nMerged Active Config:")
print(active_config)
dict_a | dict_b replaces clumsy legacy patterns like {**dict_a, **dict_b} or multi-line .update() calls.
re.match() matches strictly at index 0 of the string. If the pattern appears at character 5, re.match() returns None! Always use re.search() to search anywhere across the entire text.
Use regex to validate whether a username is valid: alphanumeric characters and underscores only, length between 4 and 16 characters (r"^[a-zA-Z0-9_]{4,16}$").
import re
def is_valid_username(username):
pattern = r'^[a-zA-Z0-9_]{4,16}$'
return bool(re.match(pattern, username))
test_users = ["balaji_dev", "a", "super_long_user_name_invalid", "alex@99", "chloe_2026"]
for u in test_users:
print(f"'{u:28}' -> {'Valid โ
' if is_valid_username(u) else 'Invalid โ'}")
Q Why should I pre-compile regex patterns with re.compile()?
If you execute a regular expression repeatedly in a loop (e.g. over 100,000 log lines), pre-compiling with pattern = re.compile(r"...") saves compilation CPU cycles on each iteration.
Q What is the difference between greedy and non-greedy regex matching?
By default, qualifiers (*, +) are greedy, matching the longest possible string. Adding ? (*?, +?) makes them non-greedy (lazy), matching the shortest possible string.
Q Can enum members be compared with "is"?
Yes! Enum members are unique singletons in Python memory, so status is OrderStatus.PENDING is fast and safe.