Exercises
Six regex drills that come up in real code: log line parsing, simple validation, identifier extraction, and the patterns that demand a real parser instead. Try first.
Six regex exercises
EXAMPLE
# ============================================================
# Drill 1 — Parse Apache log line
# ============================================================
# Sample: 203.0.113.5 - - [11/Jun/2026:10:32:14 +1000] "GET /api HTTP/1.1" 200 1532
# TASK: extract ip, ts, method, path, status, bytes.
#
# ANSWER:
import re
LOG = re.compile(r'''
^
(?P<ip>\d{1,3}(?:\.\d{1,3}){3})\s+
\S+\s+\S+\s+
\[(?P<ts>[^\]]+)\]\s+
\"(?P<method>[A-Z]+)\s+(?P<path>[^ ]+)\s+HTTP/[\d.]+\"\s+
(?P<status>\d{3})\s+(?P<bytes>\d+|-)
''', re.VERBOSE)
m = LOG.match(line)
m.group('ip'), m.group('status')
# ============================================================
# Drill 2 — Validate UUID v4
# ============================================================
# ANSWER:
UUID4 = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', re.I)
# ============================================================
# Drill 3 — Extract all #hashtags from a tweet
# ============================================================
# ANSWER:
HASHTAG = re.compile(r'#([A-Za-z_]\w{0,49})')
hashtags = HASHTAG.findall(text)
# Note: keep the hash visible? Use 'group 0' (whole match) instead of group 1.
# ============================================================
# Drill 4 — Strip HTML tags from a string
# ============================================================
# TASK: 'just a one-liner' — write a regex to remove all HTML tags.
#
# ANSWER: DO NOT use regex for this. HTML is not regular.
# Use BeautifulSoup, lxml, html5parser, or domsanitiser.
# A 'simple' regex breaks on <script>foo > 1</script>, CDATA, comments,
# attribute values containing >, etc.
# ============================================================
# Drill 5 — Extract function names from Python source
# ============================================================
# ANSWER:
DEF = re.compile(r'^\s*def\s+(\w+)\s*\(', re.MULTILINE)
names = DEF.findall(source)
# Better tool: ast module (proper parser):
# import ast; tree = ast.parse(source); [n.name for n in ast.walk(tree) if isinstance(n, ast.FunctionDef)]
# ============================================================
# Drill 6 — Match a date in ISO 8601 (yyyy-mm-dd)
# ============================================================
# ANSWER:
ISO = re.compile(r'^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$')
# Note: this matches 2026-02-30 because regex cannot count days in February.
# Validate the rest with a real date parser:
# from datetime import date
# date.fromisoformat(s) # raises on invalid days
# ============================================================
# Bonus — REDoS pattern to AVOID
# ============================================================
# r'^(a+)+$' # nested quantifier
# r'^(\w+\s+)+$' # alternation overlap
# These can take minutes to fail on adversarial input. Use atomic groups or
# the RE2 engine (Go's regexp) when patterns come from untrusted input.
# ============================================================
# Decision rules
# ============================================================
# Use regex for: line-oriented text, log parsing, extraction, validation
# Avoid regex for: HTML, XML, JSON, YAML, CSV with quoting, code parsing,
# anything that needs nested structure tracking
# ============================================================
# Scoring
# ============================================================
# 6 / 6 -> ready to review regex in PRs
# 4 / 6 -> bookmark regex/cheatsheet
# < 4 -> 'Mastering Regular Expressions' (Friedl)
Why it matters
Compile patterns once at module scope and use them many times. The engines internal cache helps a bit, but explicit compilation removes the per-call lookup entirely — and named groups make the regex self-document at the import site rather than at every call site.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…