Regex tester · Guide
Regex Tester Python: Test Python Regular Expressions Online
Python's `re` module follows the same core syntax as most flavours, so an online tester gets you most of the way. The remaining part — raw strings, how `match` differs from `search`, Python's own flag names and its substitution syntax — is exactly where a pattern that worked in the tester stops working in code. These are the differences worth knowing before you paste.
Always use raw strings
Write patterns as r"\d+", never "\d+". Without the r prefix Python processes backslash escapes first, so the regex engine receives something other than what you typed.
"\d" # DeprecationWarning, then passes '\d' through
"\b" # becomes a backspace character (0x08) — silently wrong
r"\b" # stays as the two characters \ and b — a word boundary
"\\b" # also correct, but harder to read\b is the dangerous one because it fails silently. In a normal string it becomes an ASCII backspace, so the pattern compiles, runs, and simply never matches. Nothing warns you.
This is the single most common reason a pattern copied out of a tester behaves differently in Python. The tester shows the pattern itself; the raw-string prefix is how you preserve it through Python's string literal parsing.
match, search, fullmatch and findall
Python splits into four functions where many languages have one, and picking the wrong one accounts for most of the remaining surprises.
import re
re.match(r"\d+", "abc 123") # None — anchored at the start
re.search(r"\d+", "abc 123") # <Match '123'> — scans
re.fullmatch(r"\d+", "123abc") # None — must match entirely
re.findall(r"\d+", "a1 b22 c3") # ['1', '22', '3'] — strings
re.finditer(r"\d+", "a1 b22") # match objects with positionsre.match anchors at the beginning of the string. It does not mean *does this match anywhere* — that is re.search, and search is what an online tester shows you by default. A pattern that highlights correctly in a tester and returns None from re.match is almost always this.
re.fullmatch is usually what you want for validation, because it removes the need to wrap the pattern in ^...$ and cannot be defeated by a newline the way $ can.
re.findall has a behaviour worth memorising: with no capture group it returns whole matches, with one group it returns that group, and with several it returns tuples. Adding a group to an existing pattern therefore changes the return type — a common source of breakage when a pattern is edited later.
re.findall(r"\d+-\d+", "10-20") # ['10-20']
re.findall(r"(\d+)-\d+", "10-20") # ['10']
re.findall(r"(\d+)-(\d+)", "10-20") # [('10', '20')]Python's flags
The flag names differ from the single letters most testers use, and two of them behave in ways worth knowing.
re.IGNORECASE re.I case-insensitive
re.MULTILINE re.M ^ and $ match at line boundaries
re.DOTALL re.S . also matches newline
re.VERBOSE re.X ignore whitespace, allow # comments
re.ASCII re.A \w \d \b become ASCII-only
re.search(pattern, text, re.I | re.M) # combine with |Note that re.S is DOTALL in Python, while the same letter means *single line* in several other flavours. If you carry s across from a tester, confirm which meaning it had.
re.ASCII matters more than it looks. By default Python 3 makes \w, \d and \b Unicode-aware, so \d matches Arabic-Indic and Devanagari digits, and \w matches accented letters. That is usually what you want for text and definitely not what you want for validating an identifier or parsing a numeric field.
re.fullmatch(r"\d+", "١٢٣") # matches — Arabic-Indic digits
re.fullmatch(r"\d+", "١٢٣", re.A) # None
re.fullmatch(r"[0-9]+", "١٢٣") # None — explicit is saferre.VERBOSE is Python's best feature here. It ignores whitespace and allows # comments inside the pattern, which turns an unreadable one-liner into something a reviewer can check.
pattern = re.compile(r"""
(?P<year>\d{4}) - # four-digit year
(?P<month>\d{2}) - # month
(?P<day>\d{2}) # day
""", re.VERBOSE)In verbose mode a literal space must be escaped as \ or written as [ ], since unescaped whitespace is now ignored.
Flags can also be set inline at the start of a pattern — (?i) or (?is) — which is how you carry them across from a tester that accepts only a pattern string with no flag field.
Named groups and substitution
Python names groups with (?P<name>...). The P is required and is Python-specific; the (?<name>...) form used by JavaScript and .NET is a syntax error here.
m = re.search(r"(?P<user>\w+)@(?P<host>[\w.]+)", "ada@example.com")
m.group("user") # 'ada'
m.groupdict() # {'user': 'ada', 'host': 'example.com'}
m.span("host") # (4, 15)Substitution uses backslash references, not dollar signs. A replacement string copied from JavaScript inserts a literal $1 instead of the captured text — and because that is a valid string, nothing raises an error.
re.sub(r"(\d+)-(\d+)", r"\2-\1", "10-20") # '20-10'
re.sub(r"(?P<a>\d+)", r"[\g<a>]", "42") # '[42]'
re.sub(r"(\d+)", "$1", "42") # '$1' — wrongre.sub also accepts a function as the replacement, which is far clearer than an dense pattern when the transformation has conditions.
re.sub(r"\d+", lambda m: str(int(m.group()) * 2), "a1 b2")
# 'a2 b4'Compiling, and when it matters
re.compile returns a reusable pattern object. Python caches compiled patterns internally, so the performance argument is weaker than it is often stated — the cache holds 512 entries and covers most programs.
EMAIL = re.compile(r"[^@\s]+@[^@\s]+\.\w+")
if EMAIL.fullmatch(value):
...Compiling is still worth it for two reasons that have nothing to do with speed: it names the pattern, and it puts it at module level where it is visible in review rather than buried in a loop. The cache is also bypassed when a pattern is built by string interpolation, and there the cost is real.
If you interpolate user input into a pattern, re.escape it. Without that, a value containing ( or [ either raises or silently changes what the pattern matches.
re.search(re.escape(user_input) + r"\d+", text)Catastrophic backtracking
Python's re uses a backtracking engine with no built-in timeout, so a pattern with nested quantifiers over a non-matching string can hang the process.
re.match(r"(a+)+$", "a" * 30 + "b") # effectively never returnsThe shape to watch for is a quantifier applied to a group that itself contains a quantifier — (a+)+, (\w*\s?)*, (.*,)*. Each additional character roughly doubles the work.
The fixes are to make the inner part more specific so alternatives cannot overlap, to anchor the pattern, or to stop using a regex where a split would do. If you must run untrusted patterns, the third-party regex module supports a timeout; the standard library does not.
Frequently asked questions
Why does my pattern match in the tester but not in Python?
Two causes cover nearly all cases. The pattern was not written as a raw string, so Python consumed the backslashes before the regex engine saw them — "\b" becomes a backspace character and never matches. Or the code uses re.match, which anchors at the start of the string, where the tester was showing re.search behaviour.
What is the difference between re.match, re.search and re.fullmatch?
re.match anchors at the beginning and matches a prefix. re.search scans the whole string for the first match anywhere. re.fullmatch requires the pattern to consume the entire string, which makes it the right choice for validation because it needs no ^...$ wrapping.
How do I make . match a newline in Python?
Pass re.DOTALL, also written re.S, or set it inline with (?s) at the start of the pattern. Be aware that s means DOTALL in Python but *single line* in some other flavours, so verify the meaning when copying a pattern across.
Why does re.findall return tuples?
Its return type depends on how many capture groups the pattern has: no groups returns whole matches, one group returns that group, and two or more return tuples. Adding a group to an existing pattern therefore changes the return type. Use re.finditer when you want match objects regardless of grouping.
Should I compile my regex patterns?
Python caches compiled patterns automatically, so the speed benefit is small for most code. Compile at module level anyway when a pattern is reused — it gives the pattern a name and makes it visible in review. The cache does not help patterns built by string interpolation, where compiling genuinely matters.
Why does \d match non-ASCII digits?
Python 3 makes \w, \d and \b Unicode-aware by default, so \d matches Arabic-Indic and Devanagari digits among others. Pass re.ASCII to restrict them, or write [0-9] explicitly — the explicit form is clearer and cannot be changed by a flag somewhere else.
Ready to try it?
Open the free browser-based Regex tester and apply what you just read — no sign-up, runs locally.
Open the Regex tester tool