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.
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
| Name | Type | Required | Description |
|---|---|---|---|
| msg | str | yes | The bare message, without the position text. |
| pattern | str | bytes | no (None) | The pattern (or replacement template) being parsed. |
| pos | int | no (None) | Index in pattern where the error was detected; adds "at position N" to the message. |
Attributes
| Attribute | Type | Meaning |
|---|---|---|
| msg | str | The unformatted message, e.g. "nothing to repeat" |
| pattern | str | None | The regular expression (or repl template) that failed |
| pos | int | None | Index in pattern where compilation failed — None for errors found after parsing |
| lineno | int | None | Line of pos, counting from 1 |
| colno | int | None | Column 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 02. Nothing to repeat
import re
re.compile('*.txt')
Returns
re.PatternError: nothing to repeat at position 03. 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 pattern6. error is PatternError
import re
re.error is re.PatternError
Returns
True7. Templates raise it too
import re
re.sub(r'(a)', r'\2', 'a')
Returns
re.PatternError: invalid group reference 2 at position 1Pitfalls
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.