NOETRION

Choose a topic

DAY 24 OF 30 · OBJECTS AND USEFUL TOOLS

Regular expressions

Match structured text with bounded patterns and test captures, substitutions, and near misses.

90–120 minutes suggested study timePython 3.12Not yet completed

Today's outcomes

  • Choose fullmatch, search, or findall deliberately.
  • Use raw patterns and capturing groups.
  • Test negative cases instead of trusting a successful example.

Before you start: Complete Day 23 and its practical challenge.

Understand the concept

Match the operation to the question

A regular expression describes a text pattern. re.fullmatch asks whether the entire supplied string matches. re.search looks for a match somewhere inside it. re.findall collects matched text or captured groups, depending on the pattern. These operations answer different questions even when they use the same pattern.

If a field must be a session ID consisting of S and exactly three ASCII digits, fullmatch expresses the whole-field requirement. Searching for that pattern would also succeed inside unrelated surrounding text. A successful search therefore does not establish that the complete field is a valid ID.

Regex is useful for a bounded structure. It is not a substitute for a date parser, a JSON parser, or a full HTML parser. A date-shaped string can still contain February 30. A pattern match establishes syntax under the chosen pattern, not the meaning of every field.

Raw literals and regex syntax are separate layers

Raw strings make backslashes easier to read in many patterns by reducing Python literal escape interpretation. The regex engine still interprets its own escapes. r'\b' gives the engine a word-boundary escape, while a nonraw string spelling can introduce a backspace character instead.

Use [0-9] when the contract requires ASCII digits. \d in Python's ordinary Unicode matching can cover other decimal digits too. Neither choice is universally better; they express different accepted domains. State the domain for an identifier rather than assuming “digit” has only one interpretation.

Groups extract parts of an established match

Parentheses define capturing groups. After a successful match, group(0) is the entire matched text and group(1) is the first captured group. A failed match returns None, so check it before calling group. Named groups can make a multi-field pattern easier to read.

Quantifiers specify repetition: {3} means exactly three, + means one or more, and * means zero or more. Character classes select allowed characters. A dot has its own matching rules and is not automatically a literal period. Escape a literal period when your pattern requires one.

Worked example

import re

pattern = re.compile(r"S([0-9]{3})")
valid = pattern.fullmatch("S007")
assert valid is not None
assert valid.group(0) == "S007"
assert valid.group(1) == "007"
assert pattern.fullmatch("prefix S007") is None
assert pattern.fullmatch("S0079") is None

text = "Recorded S007 and S123; not S0079."
ids = re.findall(r"\bS[0-9]{3}\b", text)
assert ids == ["S007", "S123"]
collapsed = re.sub(r"\s+", " ", " read\n  code\t review ").strip()
assert collapsed == "read code review"
print("IDs:", ids)
print("Collapsed text:", collapsed)
Operation Result Meaning
Fullmatch S007 Match Entire field has required structure
Fullmatch prefix S007 None Surrounding text violates the field contract
Fullmatch S0079 None Four digits are not three
Find IDs in sentence S007, S123 Valid bounded tokens found inside text
Substitute whitespace runs One space per run Newlines are intentionally collapsed too

The digit group stays text, preserving its leading zeros. Converting it to 7 would lose the exact identifier spelling. The whitespace substitution is a policy for a single-line display, not a general safe transformation for a file whose line boundaries carry meaning.

Guided practice

  1. Predict: compare findall with no capture, one capture, and two captures. Observe how its result shape changes. Do not assume it always returns the full matched text.
  2. Modify: allow IDs beginning with S or T while keeping exactly three ASCII digits. Test a lowercase s, a fourth digit, and an embedded prefix to confirm the boundaries remain deliberate.
  3. Reconstruct: parse an ASCII label of three uppercase letters, a hyphen, and two digits. Use fullmatch and a named group for the numeric text. Test near misses, not just one accepted label.

Keep a small negative-case list next to every pattern. It documents the intended language more effectively than a long comment saying “validates everything.”

Daily challenge

Implement parse_session_code(text). Accept exactly DAY- followed by two ASCII digits representing a day from 01 through 30. Return the integer day. Reject surrounding spaces, lowercase prefixes, extra characters, 00, and 31. Raise ValueError for any string that fails syntax or range.

Input Expected outcome
DAY-01 1
DAY-30 30
DAY-00 or DAY-31 ValueError after numeric range check
day-01 or DAY-1 ValueError from syntax contract
space followed by DAY-01 ValueError; no implicit trimming

The explicit range check demonstrates the boundary between regex structure and application meaning. A two-digit match alone cannot establish that the day exists in this course.

Reference solution

Compare after attempting the challenge
import re

def parse_session_code(text):
    match = re.fullmatch(r"DAY-([0-9]{2})", text)
    if match is None:
        raise ValueError("expected DAY- plus two ASCII digits")
    day = int(match.group(1))
    if not 1 <= day <= 30:
        raise ValueError("day must be between 01 and 30")
    return day

assert parse_session_code("DAY-01") == 1
assert parse_session_code("DAY-30") == 30
for text in ["DAY-00", "DAY-31", "day-01", "DAY-1", " DAY-01", "DAY-01x"]:
    try:
        parse_session_code(text)
    except ValueError:
        pass
    else:
        raise AssertionError("Near miss should be rejected")
print("Pattern and semantic range passed")

This function's accepted input type is str. Arbitrary nonstring objects require a separately stated type policy. The pattern is deliberately small and bounded; this exercise does not promise a universal ID parser.

Debug and explain

If group access fails, check whether the match was None. If an invalid field passes, inspect search versus fullmatch. If captured results have an unexpected shape, inspect the number of groups. If the pattern behaves differently in a browser tool, remember that JavaScript and Python regex engines have differences.

Explain why DAY-31 needs a numeric rule after matching, why surrounding spaces are rejected here but trimmed in an earlier label policy, and why leading zeros are preserved until the numeric day interpretation becomes appropriate.

Review and next step

Save the accepted and near-miss cases. This checkpoint completes objects, date types, and text patterns. Day 25 introduces JSON, where a real parser and explicit shape validation replace ad hoc string matching.

Sources and verification

Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.

DAILY CHECKPOINT

Check your understanding

Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.

01. Which operation checks an entire field against a pattern?

02. What does a failed regex match return?

03. Why use [0-9] for an ASCII-only ID?

04. Why does DAY-31 require a range check after matching two digits?

05. What can capturing groups change about findall results?

Review your practical work

Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.

Pass the knowledge check and finish the practical self-review to save completion.