The match table shows up to 1,000 matches. Replacement output includes every match and is limited to 2 million characters. Runs that exceed the time or output limit report an error.
Python regex syntax at a glance
| Syntax | Meaning |
|---|---|
(?P<name>…) | Named group; read it with m.group('name') or m['name'] |
(?P=name) | Backreference to a named group |
\1, \g<1>, \g<name> | Group references in re.sub replacements; \g<0> is the whole match |
(?:…) | Group without capturing |
(?=…), (?!…), (?<=…), (?<!…) | Lookahead and lookbehind (lookbehind must have a fixed width in Python) |
(?i), (?x), (?a) … | Inline flags; must be at the start of the pattern in Python 3.11+ |
(?i:…) | Flags for part of the pattern |
\A, \Z | Start and end of the string, regardless of MULTILINE |
a{,3} | Up to 3 (Python allows an empty minimum; JavaScript does not) |
(?#…) | Comment |
Using the pattern in Python
import re
EMAIL = re.compile(r"(?P<user>[\w.+-]+)@(?P<domain>[\w-]+(?:\.[\w-]+)+)")
text = "Contact ada@example.com or grace@navy.mil"
EMAIL.findall(text) # [('ada', 'example.com'), ('grace', 'navy.mil')]
[m.group(0) for m in EMAIL.finditer(text)] # ['ada@example.com', 'grace@navy.mil']
m = EMAIL.search(text)
m['domain'], m.start(), m.end() # ('example.com', 8, 23)
EMAIL.sub(r"\g<user> at \g<domain>", text)
EMAIL.sub(lambda m: m['user'].upper(), text) # function replacement
findall returns tuples as soon as the pattern has groups, which surprises many people. Use non-capturing
groups (?:…) for grouping you don’t need to read, or use finditer.
Verbose patterns
With re.VERBOSE (flag X), whitespace is ignored and # starts a comment, so long patterns can be laid out and explained:
PHONE = re.compile(r"""
\(? (\d{3}) \)? # area code, optional parentheses
[\s.-]? # separator
(\d{3}) # exchange
[\s.-]?
(\d{4}) # line number
""", re.VERBOSE)
Writing the same pattern for JavaScript or Go? Use the JavaScript regex tester or the Go regex tester; the syntax differences are flagged as errors.
Frequently asked questions
Does this run real Python?
No. Supported patterns are translated and run locally in a browser worker. Python Unicode case equivalence, ASCII mode and verbose whitespace are handled separately from JavaScript rules, with results checked against CPython fixtures. Atomic groups, possessive quantifiers, case-insensitive backreferences and repeated capturing groups are rejected. Optional capturing groups that can match empty are also rejected because their captures cannot be reliably reproduced. Use non-capturing groups when you only need whole matches, or run native Python for these patterns.
What is the difference between re.match, re.search and re.findall?
re.match only matches at the start of the string, re.search finds the first match anywhere, and re.fullmatch requires the whole string to match. re.findall returns all matches as strings (or tuples of groups if the pattern has groups), and re.finditer yields match objects with positions. This tester shows every match, like finditer.
Why does \d match Arabic or Devanagari digits?
In Python 3, str patterns are Unicode-aware by default: \d matches any decimal digit such as ٣ or ४, \w matches letters and digits in every script, and \s includes Unicode spaces. Add the re.ASCII flag (A above, or (?a) in the pattern) to restrict them to [0-9], [a-zA-Z0-9_] and ASCII whitespace. JavaScript is the opposite: ASCII by default.
Why does $ match before a trailing newline?
In Python, $ matches at the end of the string and also just before a newline at the very end, so "end$" matches "the end\n". Use \Z (or \z in Python 3.14) to match only at the absolute end. With re.MULTILINE, $ matches before every \n.
Why should I use raw strings for patterns?
In a normal Python string, backslashes are processed before the regex sees them: "\b" is a backspace character, not a word boundary. A raw string r"\b\d+\b" passes the backslashes through unchanged. Python 3.12+ warns about invalid escapes like "\d" in normal strings.