AlgoDaily

Modifiers and Lookahead/Lookbehind

Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.

Beyond Matching

Orientation

Now that you've finished Grouping and Capturing, you can already describe what to match and what pieces to extract.

This lesson adds two power tools:

  • Modifiers / flags: change how the regex engine interprets the pattern, such as case sensitivity or line behavior.
  • Lookahead and lookbehind: assert context around a match without including that context in the final match.

For intermediate Python work—especially text preprocessing for machine learning—these features let you write patterns that are more precise without over-capturing noisy context.

Beyond Matching

Modifiers As Lenses

Flags change the matching environment

In Python's re module, modifiers are usually passed as flags.

Common flags:

  • re.IGNORECASE or re.I: match letters case-insensitively
  • re.MULTILINE or re.M: make ^ and $ work per line, not just for the whole string
  • re.DOTALL or re.S: make . match newlines too
  • re.VERBOSE or re.X: allow whitespace and comments inside a regex

Think of flags as lenses placed over the pattern. The pattern may look the same, but the engine interprets it differently.

```python
import re

text = "Error: disk full\nwarning: slow batch\nERROR: model failed"

print("Case-sensitive:", re.findall(r"error", text))
print("Ignore case:", re.findall(r"error", text, flags=re.IGNORECASE))

print("Line starts without MULTILINE:", re.findall(r"^ERROR", text))
print("Line starts with MULTILINE:", re.findall(r"^ERROR", text, flags=re.MULTILINE))
```

Line Flag Check

Which Python regex flag makes ^ and $ apply to the start and end of each line inside a multi-line string?

Inline Modifiers

Flags can live inside patterns

Python also supports inline modifiers.

Examples:

  • (?i)cat means match cat case-insensitively from that point onward.
  • (?m)^error enables multi-line anchor behavior inside the pattern.
  • (?i:cat) applies case-insensitivity only inside that group.

That last form is especially useful when only one part of a larger pattern needs different behavior.

For example, in a training dataset label parser, you might want label to be case-insensitive while keeping the value strict:

(?i:label)=([A-Z_]+)

This matches label=SPAM, LABEL=SPAM, or Label=SPAM, while still requiring the captured value to be uppercase.

Inline Modifiers

Scoped Flag Blank

Fill in the regex that matches cat, CAT, or Cat using a scoped inline case-insensitive modifier around only the word cat.

Answer with the regex only.

Fill in the blank: _____________

Lookahead Assertions

Match something only if the right context exists

A lookahead checks what comes after the current position without consuming it.

Syntax:

  • Positive lookahead: X(?=Y) means match X only if Y follows.
  • Negative lookahead: X(?!Y) means match X only if Y does not follow.

This is useful when the context is a condition, not part of the value you want back.

In ML preprocessing, that might mean extracting a token only when it appears before a particular label, status, or annotation.

```python
import re

text = "loss=0.42 status=ok loss=0.91 status=fail loss=0.33 status=ok"

# Extract loss values only when the next status is fail.
fail_losses = re.findall(r"loss=(\d+\.\d+)(?= status=fail)", text)

# Extract loss values only when the next status is NOT fail.
non_fail_losses = re.findall(r"loss=(\d+\.\d+)(?! status=fail)", text)

print("Fail losses:", fail_losses)
print("Non-fail losses:", non_fail_losses)
```

Lookahead Consumption

True or false: A lookahead assertion checks upcoming text and consumes that text as part of the final match.

Lookbehind Assertions

Match something only if the left context exists

A lookbehind checks what comes before the current position without consuming it.

Syntax:

  • Positive lookbehind: (?<=Y)X means match X only if Y is immediately before it.
  • Negative lookbehind: (?<!Y)X means match X only if Y is not immediately before it.

Important Python rule: standard re lookbehind patterns must be fixed width.

So this is allowed:

(?<=id:)\d+

But this is not allowed in Python's built-in re:

(?<=id:\s*)\d+

because \s* can match a variable number of characters.

```python
import re

text = "id:104 label:cat id:205 label:dog temp:999"

ids = re.findall(r"(?<=id:)\d+", text)
labels = re.findall(r"(?<=label:)\w+", text)
not_temp_numbers = re.findall(r"(?<!temp:)\b\d+\b", text)

print("IDs:", ids)
print("Labels:", labels)
print("Numbers not after temp:", not_temp_numbers)
```

Python Lookbehind Rule

Which lookbehind is valid in Python's built-in re module?

Readable Advanced Regex

Use power carefully

Modifiers and lookarounds let you express conditions without bloating your match.

A practical workflow:

  1. Start with the core thing you want to match.
  2. Add anchors, boundaries, or groups from earlier lessons if needed.
  3. Use flags when the whole matching environment changes.
  4. Use lookahead/lookbehind when nearby context should be checked but not returned.
  5. If the regex becomes hard to read, consider re.VERBOSE and comments.

In the next lesson, Common Regex Patterns, you'll see how these tools combine into reusable patterns for real-world text tasks.

Readable Advanced Regex

Open interactive view →