Phone numbers arrive as 138-1234-5678, as 138 1234 5678, and once as a small poem. A pattern can find them all; splitting on spaces cannot. What exactly does the regex capture?
A regular expression describes a text pattern. Python's re module can search for the first match, collect every match, or replace matching text. Start with a raw string such as r"\d+" so backslashes reach the regex engine unchanged. You will also distinguish match, search, and fullmatch; parse named groups; and escape literals.
Search for the first match
re.search returns a match object for the first place the pattern appears, or None if there is no match. Call group() to read the matched text. Always handle the None case in real programs.
import re
match = re.search(r"\d+", "Order 42 is ready")
print(match.group())The first number
Find every match
re.findall returns a list of all non-overlapping matches. A character class such as [A-Z] matches one uppercase letter; a quantifier such as + means one or more repetitions. The result is easy to count or join.
import re
print(re.findall(r"\d+", "a1 b22 c333"))
print(re.findall(r"[A-Z]", "PyTHON"))All matching pieces
Replace matching text
re.sub replaces every match with a replacement string. Here \s+ means one or more whitespace characters, so repeated spaces collapse into one. Use a raw string for the pattern and test the replacement on a small sample first.
import re
text = "latte tea mocha"
print(re.sub(r"\s+", " ", text))Normalising whitespace
match, search, and fullmatch
match() anchors at the start; search() scans anywhere; fullmatch() requires the whole string. Choose the weakest one that expresses the rule. None is a normal result, so check it before calling group().
import re
text = "order 42 ready"
print(bool(re.match(r"\d+", text)))
print(bool(re.search(r"\d+", text)))
print(bool(re.fullmatch(r"\d+", "42")))
print(re.search(r"\d+", text).group())Prefix, anywhere, or whole field
Named groups parse a record
Named groups make a pattern readable and return a dictionary. Put the strict shape in the pattern, then read fields by name. Check for None before groupdict(); a failed match is not an empty record.
import re
pattern = re.compile(r"(?P<date>\d{4}-\d{2}-\d{2})\|(?P<drink>[a-z]+)\|(?P<price>\d+\.\d{2})")
match = pattern.fullmatch("2026-10-28|latte|4.50")
print(match.groupdict() if match else "invalid")
bad = pattern.fullmatch("2026-10-28|latte|free")
print(bad is None)A strict shape with readable fields
Escape literals and avoid greedy surprises
re.escape() makes user text safe to embed in a pattern. Greedy quantifiers take as much as possible; a trailing question mark makes them lazy. Use the narrowest pattern and test it against both a valid and an invalid sample.
import re
needle = "a.b"
text = "a.b axb"
print(re.findall(re.escape(needle), text))
html = "<b>bold</b>"
print(re.findall(r"<.+?>", html))
print(re.findall(r"<.+>", html))Escape text; lazy is not always better
Key points
- Use raw strings for regular expression patterns.
- re.search returns the first match or None.
- re.findall returns a list of all matches.
- re.sub replaces every matching part of the text.
- Use fullmatch for validation, search for discovery, and re.escape for literal text.
Common mistakes
- Forgetting the r prefix and letting Python interpret backslashes.
- Calling group() on a search that returned None.
- Using a pattern that is broader than the text you actually want.
- Building a pattern by concatenating unescaped user input.
Extract every number from "a1 b22 c333", then join the results with a comma.
Try it yourself
Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.
Practice this lesson
12 questions with immediate explanations.