Lookarounds express conditions like 'a number followed by px' or 'a password containing a digit' cleanly.
Lookarounds are zero-width assertions: they check a condition at the current position but don't include the text in the match.
(?=...)positive lookahead: what follows must match.(?!...)negative lookahead: what follows must not match.(?<=...)positive lookbehind: what precedes must match.(?<!...)negative lookbehind: what precedes must not match.
Examples:
r"\d+(?=px)"matches the number in12pxbut not the unit.r"(?<=\$)\d+"matches amounts after a dollar sign.r"\b(?!test_)\w+\.py\b"matches Python files not starting withtest_.- Stacked lookaheads at the start test several conditions independently:
r"^(?=.*\d)(?=.*[A-Z]).{8,}$"requires a digit, an uppercase letter and length 8+.
Python requires lookbehind to be fixed width: (?<=\$|USD ) fails because the alternatives differ in length (write (?:(?<=\$)|(?<=USD )) instead), and quantifiers like + aren't allowed inside. The third-party regex module lifts that restriction.
Lookarounds are great in re.sub, because they let you insert text at a position: re.sub(r"(?<=\d)(?=(\d{3})+$)", ",", "1234567") adds thousands separators.
Readability suffers quickly; when a condition is easier as a second check in Python, write the second check.
Example
import re
print(re.findall(r"\d+(?=px)", "w: 12px; h: 30em; m: 4px")) # ['12', '4']
print(re.findall(r"(?<=\$)\d+", "cost $40, tax 5, tip $6")) # ['40', '6']
print(re.findall(r"\b(?!test_)\w+\.py\b", "app.py test_app.py db.py"))
# ['app.py', 'db.py']
strong = re.compile(r"^(?=.*\d)(?=.*[A-Z]).{8,}$")
for pw in ["password1", "Password1", "Pass1"]:
print(pw, bool(strong.match(pw)))
# password1 False
# Password1 True
# Pass1 False
print(re.sub(r"(?<=\d)(?=(?:\d{3})+$)", ",", "1234567")) # 1,234,567
try:
re.compile(r"(?<=\$|USD )\d+")
except re.error as e:
print(e) # look-behind requires fixed-width patternRemember
- Lookarounds assert context without consuming characters
- Python lookbehind must be fixed width
- Stacked lookaheads test independent conditions at one position
Watch out for
- Writing a variable-width lookbehind such as
(?<=\s+), which raises re.error.