Blog · 3 min read

When a tool says “Python regex”, ask which one

Kristof Polleunis · August 19, 2026

Python has two regex engines in common use, and they are not the same language.

There's re, in the standard library. And there's regex on PyPI — a drop-in replacement that is a strict superset, maintained separately, and depended on by a lot more code than people realise.

Every regex tool I've used treats "Python" as one thing. It isn't, and the gap is bigger than a couple of flags.

Three constructs that only exist in one of them

Set operations in character classes.

[\w--\d] is meant to say "word characters, minus the digits". Here is what that one pattern actually returns — verified on Python 3.14 and regex 2026.7.19:

import re  # the standard library
import regex  # THIRD-PARTY, from PyPI: pip install regex

re.findall(r"[\w--\d]+", "abc123def")  # error: bad character range
regex.findall(r"[\w--\d]+", "abc123def")  # ['abc123def']  — everything
regex.findall(r"(?V1)[\w--\d]+", "abc123def")  # ['abc', 'def'] — what you meant

One pattern, three answers. The standard library at least fails loudly. The trap is inside regex itself: set operations only work in its V1 mode, and its default mode — V0, kept for re compatibility — quietly reads the same class as "word characters, a dash, and digits". That matches everything, including every digit you were trying to exclude.

That's the dangerous shape: not a syntax error, just a different answer.

Fuzzy matching.

regex.findall(r"(?:foobar){e<=1}", "fooba")  # matches

{e<=1} means "allow at most one error" — insertion, deletion or substitution. There is nothing like it in re. If you've ever hand-rolled Levenshtein because you needed approximate matching, regex had it built in.

Recursion.

regex.findall(r"\((?:[^()]|(?R))*\)", "a(b(c)d)e")

(?R) recurses the whole pattern, so you can match balanced parentheses — the thing every regex tutorial tells you regex cannot do. re cannot. regex can.

And one that moved

Possessive quantifiers — a*+, a++, a?+ — arrived in re with Python 3.11. Before that they were regex-only.

So "does Python support possessive quantifiers" now has three answers depending on your interpreter version and which module you imported. A tool that models "Python" as a single static flavour gets this wrong for somebody no matter what it picks.

Why this matters more than it sounds

The failure mode isn't "my pattern didn't work." That's the good case — you find out immediately.

The failure mode is a pattern that works in two places, differently. [\w--\d] is accepted by regex in both of its modes and means opposite things in them. You develop with (?V1) in a scratch file, deploy without it, and the bug surfaces on input you didn't test.

I ship a regex tool, and I modelled Python as one flavour for longer than I should have. Fixing it meant treating the engine as a runtime setting, not a static property: a toggle that changes which module the sidecar imports, which constructs the editor offers, what the generated code says, and how the explanation panel describes what you built.

That last one matters more than expected. If the tool tells you {e<=1} is a quantifier repeating a group between e and 1 times, it isn't helping — it's confidently wrong.

How to know which one you have

import re

print(re.__name__)  # 're' or 'regex' — check what you actually imported

Sounds trivial. It isn't, because import regex as re is a common idiom, and from that point on every re.something in the file is the third-party engine. Someone reading the code six months later sees re. and assumes stdlib.

If you depend on regex features, importing it as re is the pattern most likely to mislead the next reader. import regex and use regex. explicitly.

The practical rule

Before trusting any regex tool with a Python pattern, ask it which module it ran. If it can't tell you, it ran re — which is right about 90% of the time and silently wrong about the rest.

And if you're on Python 3.11+, check whether you still need regex at all. Possessive quantifiers were often the only reason it was in the dependency list.


I maintain RegexPilot, a macOS regex editor that bundles CPython and runs your pattern through the real module — re or regex, switchable — rather than approximating either.


More from this series: