Back to Blog
Python

Python re Raise Exception: What Gets Raised and When

python re raise exception: Understand when Python's re module raises re.error, how to read its attributes, and how to raise custom exceptions from regex callbacks.

re modulere.errorexception handlingregexpython 3.13
Illustration of a Python regex pattern triggering an exception, shown as an alert symbol beside a pattern string.

When you pass an invalid pattern to Python's re module, the call does not return a sentinel value. Understanding how python re raise exception works means knowing exactly when re.error fires and when it does not. re.compile, re.search, and re.sub raise re.error (aliased as re.PatternError in Python 3.13) for invalid patterns, invalid flags, and certain invalid replacement strings.

What the re Module Raises and When It Stays Silent

The re module raises re.error in three main situations: when the pattern cannot be compiled, when an invalid flag is passed, and when a replacement string references a group that does not exist. A pattern with an unmatched parenthesis or an invalid escape is the most common trigger.

import re try: re.compile(r"[a-") except re.error as exc: print(exc.msg) # human-readable error message print(exc.pattern) # the original pattern print(exc.pos) # index where compilation failed

The exception is raised at the point where the pattern is compiled, which happens either when you call re.compile explicitly or when you call a module-level function such as re.search or re.sub with a string pattern.

The re module does not raise an exception when a pattern simply fails to match. re.search returns None, re.match returns None, and re.sub returns the input string unchanged. This is a deliberate design choice, and it is the most common source of confusion for developers who expect a missing match to be an error. If a missing match must be treated as a failure, you have to check the return value yourself and raise your own exception.

The re.error Exception Object and Its Attributes

re.error carries structured information about what went wrong. The msg attribute contains the human-readable message, and when the the failure comes from pattern compilation, pattern, pos, lineno, and colno identify the offending pattern and its location.

import re try: re.compile(r"(?P<name>abc") except re.error as exc: print(exc.msg) # missing ), unterminated subpattern print(exc.pattern) # the original pattern print(exc.pos) # index where compilation failed

When the error is raised for a reason unrelated to pattern compilation, such as an invalid group reference in a replacement string, pattern may be None and pos may be 0. Code that reads these attributes should treat them as optional rather than assuming they are always populated.

Match-Time Errors in Replacement and Group References

re.error is not only raised during compilation. re.sub and match.expand validate group references in the replacement string when a match actually occurs, and they raise re.error if the replacement references a group that does not exist.

import re try: re.sub(r"(a)", r"\2", "abc") except re.error as exc: print(exc.msg) # invalid group reference 2

The same applies to match.expand and to re.sub with a named group reference such as \g<missing>. Note that match.group(99) behaves differently: it raises IndexError, not re.error, because the group number is out of range for the compiled pattern. This distinction matters when you write a single except clause, because catching re.error alone will not handle an out-of-range group access.

Catching re.error Without Swallowing Unrelated Failures

A focused except re.error is the right way to handle regex failures, because it leaves unrelated exceptions untouched. Catching a bare Exception around regex calls can hide bugs in callback functions or in surrounding code, especially when the regex operation runs inside a larger processing loop.

import re def parse_identifier(text): try: return re.match(r"[a-zA-Z_]\w*", text) except re.error: raise ValueError("invalid identifier pattern") from None

When a callback function passed to re.sub raises an exception, that exception propagates unchanged; re.sub does not wrap it in re.error. If you want a specific failure mode from inside the callback, raise the exception directly and let it escape.

Raising Exceptions From Regex Callback Functions

The replacement argument of re.sub can be a function. When that function raises, the exception propagates out of re.sub as-is, with the original traceback intact.

import re def replace(match): value = match.group(1) if not value.isdigit(): raise ValueError(f"expected digits, got {value!r}") return str(int(value) * 2) re.sub(r"(\d+)", replace, "12 34")

This is useful for validation pipelines where a malformed match should stop processing immediately rather than being silently replaced. The exception type you raise is entirely under your control; re.sub does not intercept or translate it.

Wrapping Regex Failures in Domain-Specific Exceptions

In larger codebases, leaking re.error into business logic couples callers to the re module's exception type. A common pattern is to catch re.error at the boundary and re-raise a domain exception with a clear message.

class ConfigParseError(Exception): pass def parse_config(pattern, text): try: return re.search(pattern, text) except re.error as exc: raise ConfigParseError(f"invalid config pattern: {exc.msg}") from exc

Using from exc preserves the original traceback, which helps when debugging the underlying pattern. This pattern keeps regex-specific details inside the parsing layer and gives callers a stable exception contract.

Writing Maintainable Regex Error Handling

Compiling patterns once and reusing the compiled object reduces repeated compilation work and makes error handling more predictable, because a malformed pattern fails fast at import or initialization time rather than in the middle of a request.

_PATTERN = re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})") def extract_date(text): match = _PATTERN.search(text) if match is None: raise ValueError("date not found") return match.group("year"), match.group("month")

For compatibility, note that Python 3.13 introduced re.PatternError as the canonical name for the exception and kept re.error as an alias. Code that catches re.error continues to work, but new code can reference re.PatternError directly if it targets 3.13 or later. If you support older versions, stick with re.error so the same source runs everywhere.

python re raise exception: Practical Usage and Code Examples | RYUSLOG DEV