Matching Basics
Regular Expressions in Practice ๐
Chapter 1 ยท Matching Basics
A regular expression (regex) is a small pattern language for describing text: "four digits, a dash, two digits", "a word that starts with a capital", "anything between angle brackets". Instead of writing a loop that inspects characters one at a time, you describe the shape of what you want and let the engine find it. This course is practical: each chapter takes one idea, runs it, and ends with a rule you can reuse. All examples use Python 3 and its built-in re module.
Three Ways to Ask "Does It Match?"
The re module has three entry points that differ only in where the pattern is allowed to match. Picking the wrong one is the most common beginner mistake, so learn the difference first.
| Function | Where it looks | Returns |
|---|---|---|
| re.search | anywhere in the string | first match, or None |
| re.match | only at the start of the string | match, or None |
| re.fullmatch | the whole string, start to end | match, or None |
| re.findall | everywhere | a list of every non-overlapping match |
A successful call returns a match object; a failed one returns None. Because None is falsy, if re.search(...): is the usual test. On the match object, .group() is the matched text and .span() gives the start and end positions.
Raw Strings: Always Write r"..."
Regex uses the backslash for things like \d (a digit). Python strings also use the backslash for escapes like \n. If you write the pattern as an ordinary string, Python processes the backslashes first and can change your meaning. A raw string, written with an r prefix, leaves every backslash alone and hands it to the regex engine untouched.
The Building Blocks
Most patterns are assembled from a few kinds of piece. Most characters simply match themselves; a handful are special.
| Piece | Meaning | Example |
|---|---|---|
\d \w \s | a digit; a letter, digit or underscore; whitespace | \d\d matches 42 |
. | any one character except a newline | c.t matches cat, cot |
[abc] | one character from the set | [aeiou] matches a vowel |
[^abc] | one character not in the set | [^0-9] matches a non-digit |
* + ? | zero or more; one or more; zero or one | colou?r matches both spellings |
{n} {n,m} | exactly n; between n and m repeats | \d{4} matches four digits |
^ $ | start of the string; end of the string | ^cat$ matches only cat |
\b | a word boundary (between a word character and a non-word one) | \bcat\b matches the word, not concat |
Note the last line: [^aeiou\s]+ means "one or more characters that are neither a vowel nor whitespace", so the engine returns each run of consonants it finds.
Anchors and Word Boundaries
Anchors match a position, not a character. Without them, a pattern is happy to match inside a longer word, which is rarely what you want.
Greedy Versus Lazy
Quantifiers such as * and + are greedy: they grab as much text as they can while still letting the overall pattern succeed. Adding a ? after the quantifier makes it lazy, taking as little as possible. The classic trap is matching tags.
The greedy .* ran to the very last > in the string. The lazy .*? stopped at the first one. Often a better fix than laziness is a more precise class, such as <[^>]*>, which says "anything except a closing bracket".
fullmatch to validate a whole value, search to find something inside text, findall to collect every occurrence. Always write the pattern as a raw string.
Challenges
Given log = "disk 87% full, cpu 5%, mem 100%", write one findall call that returns only the numbers (['87', '5', '100']) and a second that returns them with the percent sign (['87%', '5%', '100%']). Hint: the first can use a lookahead, (?=%), which checks that a percent sign follows without including it.
Write is_code(s) that returns True only for a product code made of exactly three capital letters, a dash and four digits (such as ABC-1234). Test it on "AB-1234", "ABC-12345", "abc-1234" and "xABC-1234". Then show what re.match does with "ABC-12345" and explain why fullmatch is the right choice.
For tags = "<p>one</p><p>two</p>", show that <p>.*</p> returns one big match. Fix it twice: once with a lazy quantifier and once with a negated class. Predict the output before you run each.
Quick Reference
- search anywhere; match at the start; fullmatch the whole string; findall every match
- Failure: returns
None, so test withif - Always write patterns as raw strings:
r"\d+" - Classes:
\d \w \s, sets[abc], negated sets[^abc] - Repeats:
* + ? {n,m}are greedy; add?for lazy - Positions:
^ $ \bmatch places, not characters