re.error

The one exception of the re module. Its message ends with "at position N" — the index in the pattern where parsing gave up — plus line and column for multi-line patterns.

InheritsBaseException›Exception›PatternError
re exceptionPython 3 (all)Live demo
re.PatternError(msgmsg — The bare message, without the position text.type: str · required, patternpattern — The pattern (or replacement template) being parsed.type: str | bytes · default: None=None, pospos — Index in pattern where the error was detected; adds "at position N" to the message.type: int · default: None=None)
Raised by
re.compile('('), re.search('*a', s), re.sub(p, r'\9', s) — any call that compiles a pattern or template
Message
missing ), unterminated subpattern at position 0
Quick fix
read the position, escape literal metacharacters (\( \* \.) or use re.escape for user text
Watch out
The traceback says re.PatternError since 3.13; except re.error catches the same class

Demo

Live evaluation
Compile a pattern. Each broken one names its problem and the position (index from 0).
Try:
Inputs
patternstra regular expression
Code
import re
re.compile('(\\d+')
Uncaught exception
re.PatternError: missing ), unterminated subpattern at position 0

Positions count from 0 in the pattern string, so "at position 2" in a** points at the second *. For a pattern containing a newline, lineno and colno (both from 1) are added to the message. Some errors are found after parsing, when the pattern is compiled — look-behind width is one — and have no position: pos, lineno and colno are None.

Parameters

NameTypeRequiredDescription
msgstryesThe bare message, without the position text.
patternstr | bytesno (None)The pattern (or replacement template) being parsed.
posintno (None)Index in pattern where the error was detected; adds "at position N" to the message.

Attributes

AttributeTypeMeaning
msgstrThe unformatted message, e.g. "nothing to repeat"
patternstr | NoneThe regular expression (or repl template) that failed
posint | NoneIndex in pattern where compilation failed — None for errors found after parsing
linenoint | NoneLine of pos, counting from 1
colnoint | NoneColumn of pos, counting from 1

Common patterns

Validate a user-supplied pattern
Compile it once and turn the error into a message.
import re
try:
    user_re = re.compile(user_input)
except re.error as e:
    raise ValueError(f'bad search pattern at position {e.pos}: {e.msg}') from e
Point at the error
Print a caret under the failing position.
import re
try:
    re.compile(p)
except re.error as e:
    print(p)
    print(' ' * (e.pos or 0) + '^', e.msg)

Examples

1. Unclosed group
import re re.compile('(abc')
Returns
re.PatternError: missing ), unterminated subpattern at position 0
2. Nothing to repeat
import re re.compile('*.txt')
Returns
re.PatternError: nothing to repeat at position 0
3. Multi-line pattern
import re re.compile('a\n(')
Returns
re.PatternError: missing ), unterminated subpattern at position 2 (line 2, column 1)
4. Attributes
import re try: re.compile('a\nb)') except re.error as e: result = (e.msg, e.pos, e.lineno, e.colno) result
Returns
('unbalanced parenthesis', 3, 2, 2)
5. Look-behind width
import re re.compile(r'(?<=a+)b')
Returns
re.PatternError: look-behind requires fixed-width pattern
6. error is PatternError
import re re.error is re.PatternError
Returns
True
7. Templates raise it too
import re re.sub(r'(a)', r'\2', 'a')
Returns
re.PatternError: invalid group reference 2 at position 1

Pitfalls

1. Regex metacharacters meant literally
* . ( ) [ ] { } ? + | ^ $ \ are syntax. Escape them to match the character itself.
'*.txt'
import re
re.findall('*.txt', 'a.txt b.txt')
re.PatternError: nothing to repeat at position 0
r'\w+\.txt'
import re
re.findall(r'\w+\.txt', 'a.txt b.txt')
['a.txt', 'b.txt']
2. Variable-width lookbehind
Python needs to know how far back to look. Use alternatives of fixed width, or move the check into a group.
(?<=\$\d+)
import re
re.findall(r'(?<=\$\d+)\.\d\d', '$19.99')
re.PatternError: look-behind requires fixed-width pattern
group instead
import re
re.findall(r'\$\d+(\.\d\d)', '$19.99')
['.99']
3. Catching the wrong exception
Flag misuse is not a pattern error: re.LOCALE with a str pattern raises ValueError.
except re.error
import re
try:
    re.compile('a', re.LOCALE)
except re.error:
    print('bad pattern')
ValueError: cannot use LOCALE flag with a str pattern
catch both
import re
try:
    re.compile('a', re.LOCALE)
except (re.error, ValueError) as e:
    print('rejected:', e)
rejected: cannot use LOCALE flag with a str pattern

When to use

Use it
  • Catch re.error around patterns built from user input or config
  • Report e.pos / e.lineno / e.colno to point at the mistake
Reach for something else
  • Catching it around fixed patterns in your own code — fix the pattern instead
  • Expecting it for bad flags (ValueError), bad argument types (TypeError) or unknown \g<name> in sub (IndexError)

Notes

CPython impl
Defined in Lib/re/_constants.py; __module__ is set to "re", which is why tracebacks show re.PatternError
Rename
Python 3.13 renamed re.error to re.PatternError and kept error as an alias — the same class object
Attributes
msg, pattern, pos, lineno, colno were added in Python 3.5
Positions
pos counts characters of the pattern from 0; lineno/colno count from 1 and appear in the message only when the pattern contains a newline

FAQ

A quantifier (*, +, ?, {n}) has nothing before it to apply to — for example a pattern starting with * like '*.txt', or a quantifier right after ^ or |. Escape it (\*) if you meant a literal asterisk.