Python: use re.prefixmatch() instead of re.match() from Python 3.15

Regexesssssssss.

Take this validation function:

import re

TRAIN_NUMBER_RE = re.compile(r"\d{6}")  # six digits


def is_valid_train_number(value: str) -> bool:
    return bool(TRAIN_NUMBER_RE.match(value))

It looks reasonable, and it works for the intended cases:

>>> is_valid_train_number("345071")
True
>>> is_valid_train_number("ABC123")
False

But, woah, it also accepts garbage suffixes:

>>> is_valid_train_number("345071-in-abbey-wood")
True

That is a bug, totally not what the author intended. re.Pattern.match() (and its module-level shortcut, re.match()) only anchors at the start of the string. It happily reports a match as soon as it finds 345071 at the beginning, regardless of what comes after.

If you want to check that the entire string conforms to the pattern, you need fullmatch() instead:

def is_valid_train_number(value: str) -> bool:
    return bool(TRAIN_NUMBER_RE.match(value))

…then you’ll see:

>>> is_valid_train_number("345071-in-abbey-wood")
False
>>> is_valid_train_number("345071")
True

So, there’s a bit of name confusion with match(). In many other regex implementations, like JavaScript, PCRE, and Ruby, the “match” method name means “match this pattern somewhere in the string”. Python has this behaviour as search(), and instead its match() means “match this pattern as a prefix of the string”, which programmers may not even realize until they hit a bug like the above.

Introducing re.prefixmatch()

Python 3.15 (expected October 2026) adds re.prefixmatch() and re.Pattern.prefixmatch(), as exact synonyms for re.match() and re.Pattern.match() respectively. The new name describes what the function actually does: match a prefix of the string, not the whole thing, and not “anywhere”. Hopefully, this will clear up the name confusion.

So now you could run:

>>> TRAIN_NUMBER_RE.prefixmatch("345071-in-abbey-wood")
<re.Match object; span=(0, 6), match='345071'>
>>> re.prefixmatch(r"\d{6}", "345071-in-abbey-wood")
<re.Match object; span=(0, 6), match='345071'>

Note that the behaviour is unchanged and the garbage suffix is still accepted. prefixmatch() doesn’t fix the validation bug discussed above—fullmatch() is still the solution. The new name only makes the prefix behaviour obvious at the call site, so you can spot such bugs when reading the code, or authors can make clear they really do intend to match a prefix.

Alongside the new name, Python’s documentation now describes match() as “soft deprecated” in favour of prefixmatch():

Soft deprecated since version 3.15: match() has been soft deprecated in favor of the alternate prefixmatch() name of this API which is more explicitly descriptive. Use it to better express intent. The norm in other languages and regular expression implementations is to use the term match to refer to the behavior of what Python has always called search().

…along with a link to a longer discussion on why.

Soft deprecated means that the old name, match(), does not emit a warning, nor are there any plans to add a warning or remove the method. Existing code will continue to work without issue, but you should prefer the new name prefixmatch() going forwards, when your code supports Python 3.15+.

Potentially, linters will grow to enforce the new prefixmatch() name over match(), to make the behaviour obvious, but it’s too early for that at time of writing, as Python 3.15 isn’t even out yet.

Fin

May your method names be precise and your regexes concise,

—Adam


Read my book Boost Your Django DX, freshly updated in November 2024.


Subscribe via RSS, Twitter, Mastodon, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: