Lookaround in Regular Expressions
Lookaround assertions (also called zero-width assertions) let you check what comes before or after a match without consuming those characters. They are one of the most powerful and elegant features of Python’s re module. In data science, lookaround is essential for precise text extraction — for example, finding numbers followed by “USD” but not “EUR”, extracting words that appear before a specific keyword, or validating patterns without including surrounding text.
TL;DR — Four Lookaround Assertions
(?=...)→ positive lookahead(?!...)→ negative lookahead(?<=...)→ positive lookbehind(?<!...)→ negative lookbehind
1. Basic Lookaround
import re
text = "Price: 1250 USD, Tax: 87 EUR, Total: 1337 USD"
# Positive lookahead: numbers followed by USD
print(re.findall(r"\d+(?= USD)", text)) # ['1250', '1337']
# Negative lookahead: whole numbers NOT followed by EUR
# (\b stops the engine from backtracking to "8" inside "87")
print(re.findall(r"\b\d+\b(?! EUR)", text)) # ['1250', '1337']
# Positive lookbehind: number preceded by "Price: "
print(re.findall(r"(?<=Price: )\d+", text)) # ['1250']
2. Real-World Data Science Examples with Pandas
import pandas as pd
# Demo rows so this example runs as-is (in your project: df = pd.read_csv("logs.csv"))
df = pd.DataFrame({"log": [
"Price: 1,250.75 USD for ORD-12345 2026-03-19 alice@example.com verified SKU: ABC-123 in stock",
"Tax: 87.50 EUR for ORD-98765 2026-03-20 bob@spam.ru SKU: XYZ-789 out of stock",
"Cancelled ORD-55555 Price: 12.00 GBP carol@data.org Order ID: 777",
]})
# Example 1: Extract amounts only in USD (positive lookahead)
df["usd_amount"] = df["log"].str.extract(r"(\d+(?:,\d+)?(?:\.\d+)?)(?= USD)")
# Example 2: Extract emails that do NOT end with .ru (negative lookahead)
df["valid_email"] = df["log"].str.extract(r"(\S+@\S+\.(?!ru\b)[a-z]+)")
# Example 3: Extract product codes preceded by "SKU: " (positive lookbehind)
df["sku"] = df["log"].str.extract(r"(?<=SKU: )([A-Z0-9-]+)")
print(df[["usd_amount", "valid_email", "sku"]])
3. Advanced Lookaround Combinations
# Lookbehind + lookahead for precise context
text = "Profit: +1250 USD, Loss: -340 EUR"
print(re.findall(r"(?<=[+-])\d+(?= USD)", text)) # ['1250']
# Python's re needs fixed-width lookbehind, so (?<=Price:\s*) raises an error.
# Match the variable part outside the lookbehind and capture the number instead:
print(re.findall(r"Price:\s*(\d+)", "Price: 1250 USD")) # ['1250']
4. Best Practices in 2026
- Use positive lookahead
(?=...)when you need to “peek ahead” without consuming - Use negative lookahead
(?!...)to exclude unwanted patterns - Remember lookbehind must be fixed-width in Python's
remodule (the third-partyregexmodule allows variable width) - Combine with pandas
.str.extract()for vectorized zero-width assertions - Keep lookaround assertions simple — they are powerful but can reduce readability if overused
Lookarounds Check Context Without Consuming It
A lookaround tests what comes before or after the current position but does not include that text in the match. There are four kinds: positive look-ahead (?=...), negative look-ahead (?!...), positive look-behind (?<=...) and negative look-behind (?<!...).
Because lookarounds are zero-width, they are ideal when the context decides whether a value counts but should not be part of the extracted value, such as a number followed by a currency or an ID not preceded by a cancelled flag.
One limit is important in Python: the standard re module only allows fixed-width look-behind. A pattern such as (?<=Price:\s*) raises an error, so use a capturing group instead, or the third-party regex module if you truly need variable width.
Worked Example: All Four Lookarounds on One String
We run the four kinds of lookaround on the same short text to compare what each returns.
import re
text = "USD 100, EUR 250, USD 75, fee 30 EUR"
print(re.findall(r"\b\d+\b(?= EUR)", text)) # number followed by EUR
print(re.findall(r"\b\d+\b(?! EUR)", text)) # number not followed by EUR
print(re.findall(r"(?<=USD )\d+", text)) # number after USD
print(re.findall(r"(?<!USD )\b\d+", text)) # number not after USD
The four calls return ['30'], ['100', '250', '75'], ['100', '75'] and ['250', '30']. In every case the currency text is used as context only and never appears in the results.
Practical Tips
Write a one-line comment next to every lookaround that says in plain words what it checks, such as "number followed by EUR". Lookarounds are compact, and a short comment saves the next reader from decoding them symbol by symbol.
Common Mistakes
- Expecting the lookaround text to be part of the match.
- Writing variable-width look-behind with the standard re module.
- Forgetting \b, so the engine backtracks and returns part of a number.
- Stacking many lookarounds when a simple capturing group would be clearer.
Quick Summary
- Lookarounds are zero-width checks.
- Ahead: (?=...) and (?!...); behind: (?<=...) and (?<!...).
- Python's re needs fixed-width look-behind.
- Add word boundaries to avoid partial numbers.
Conclusion
Lookaround assertions are zero-width superpowers that let you add context to your matches without including it in the result. In 2026 data science projects they are indispensable for precise, context-aware text extraction from logs, reports, and unstructured data. Master positive/negative lookahead and lookbehind, combine them with pandas vectorized methods, and your regex pipelines will become significantly more accurate and professional.
Next steps:
- Review one of your current regex patterns and add a lookaround assertion to make the extraction more precise