# \`fnmatch.translate\` and anchoring

**URL:** <https://discuss.python.org/t/fnmatch-translate-and-anchoring/109239>\
**Category:** Ideas\
**Created:** [September 25, 2026, 8:16pm UTC](https://discuss.python.org/t/fnmatch-translate-and-anchoring/109239 "2026-09-25T20:16:25Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![pekkaklarck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pekkaklarck/32/7211_2.png) [@pekkaklarck](https://discuss.python.org/u/pekkaklarck)\
**Post date:** [September 25, 2026, 8:16pm UTC](https://discuss.python.org/t/fnmatch-translate-and-anchoring/109239/1 "2026-09-25T20:16:25Z")

</div>

[fnmatch.translate](https://docs.python.org/3/library/fnmatch.html#fnmatch.translate) can be used for converting glob patterns to regular expressions. It’s especially convenient if you want your tool to support pattern matching, but don’t want to confuse users with regular expressions.

A problem with the function is that, because it is designed to be used with [re.match](https://docs.python.org/3/library/re.html#re.match) (soon to be also known as [re.prefixmatch](https://docs.python.org/3.15/library/re.html#re.prefixmatch)), it doesn’t work well with [re.search](https://docs.python.org/3/library/re.html#re.search). The reason is that it adds anchoring to the end:

```python-repl
>>> fnmatch.translate("Py*n")
'(?s:Py.*n)\\Z'
>>> reobj = re.compile(_)
>>> reobj.search("Monty Python")
<re.Match object; span=(6, 12), match='Python'>
>>> reobj.search("Python Discussions")
>>> 

```

The pattern matching if there’s something before the matched part, but not if there’s something after it, is pretty confusing. I can see it causing similar issues that `re.match` has caused and why the `re.prefixmatch` alias was added after a [discussion here](https://discuss.python.org/t/add-re-prefixmatch-deprecate-re-match/105927).

If you know about this behavior, you can add `*` at the end of the glob pattern, but that slows down pattern matching compared to not having anchors at all:

```python-repl
>>> fnmatch.translate("Py*n*")
'(?s:Py(?>.*?n).*)\\Z'
>>> reobj = re.compile(_)
>>> reobj.search("Monty Python")
<re.Match object; span=(6, 12), match='Python'>
>>> reobj.search("Python Discussions")
<re.Match object; span=(0, 18), match='Python Discussions'>
>>> 
>>> timeit(lambda: reobj.search("Python Discussions"))
0.4795150120044127
>>> 
>>> no_anchor = re.compile("(?s:Py.*n)")
>>> timeit(lambda: no_anchor.search("Python Discussions"))
0.23413758003152907

```

I propose enhancing the function so that we make anchoring configurable. I see two possibilities:

1. Add new parameter `anchoring: bool | None = None` that can be used like `anchoring=True` to anchor both ends and `anchoring=False` to disable anchoring altogether. The default behavior would be preserving the current functionality for backwards compatibility. I believe I’d typically use `anchoring=False` and then use either `re.search` or `re.fullmatch` depending on the context.
2. Another alternative would be adding `anchor_start: bool = False, anchor_end: bool = True`. This would be even more flexible than the earlier alternative, but due to backwards compatibility defaults would need to be inconsistent. It would be somewhat strange that if you use `anchor_end=False`, neither end is anchored.

How do others feel about this? Should I submit an issue?

---

<div class="post-metadata">

**Author:** ![dr\_carlos](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dr_carlos/32/30461_2.png) [@dr\_carlos](https://discuss.python.org/u/dr_carlos)\
**Post date:** [September 25, 2026, 11:47pm UTC](https://discuss.python.org/t/fnmatch-translate-and-anchoring/109239/2 "2026-09-25T23:47:52Z")

</div>

To suggest another bikeshed colour, I wonder if using prefix/suffix language, linking it to regex, might be more understandable? I.e., go with number 2 but name them `is_prefix` or `prefixmatch` or similar, and equivently for suffix.

The issue then from my end is that a word is not usually both a suffix and a prefix, which might imply mutual exclusivity of the keyword arguments.

---

<div class="post-metadata">

**Author:** ![pekkaklarck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pekkaklarck/32/7211_2.png) [@pekkaklarck](https://discuss.python.org/u/pekkaklarck)\
**Post date:** [September 28, 2026, 12:40pm UTC](https://discuss.python.org/t/fnmatch-translate-and-anchoring/109239/3 "2026-09-28T12:40:54Z")

</div>

I don’t particularly care about the bikeshed color, but I believe the typical use case is disabling anchoring altogether and then using `re.search`, `re.fullmatch` or `re.(prefix)match` depending on the context. With that in mind, `fnmatch.translate(pattern, anchoring=False)` looks good to me. Full anchoring with `anchoring=True` looks fine too, but in most cases passing the pattern `re.fullmatch` would be simpler. `anchoring=None` would look strange, but there are no real needs to use `None` other than as the backwards-compatibility preserving default value.

---

<div class="post-metadata">

**Author:** ![pekkaklarck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pekkaklarck/32/7211_2.png) [@pekkaklarck](https://discuss.python.org/u/pekkaklarck)\
**Post date:** [September 28, 2026, 1:49pm UTC](https://discuss.python.org/t/fnmatch-translate-and-anchoring/109239/4 "2026-09-28T13:49:58Z")

</div>

The reason I’m looking at this is that our tool has a grep-like utility with this code:

```python
pattern = fnmatch.translate(f"{pattern}*")

```

The pattern is then a bit later used with `re.search` for matching lines. I wanted to make it explicit that we are not looking for an anchored match and changed the code to:

```python
pattern = fnmatch.translate(f"*{pattern}*")

```

To my surprise, a user reported a [huge performance regression](https://github.com/robotframework/robotframework/issues/5794). The numbers apparently vary depending on the pattern and the file size, but in one case the old code took 9ms while the new one took 55s!

I was curious why the change could be so big and decided to look at the `fnmatch.translate` [source code](https://github.com/python/cpython/blob/3.14/Lib/fnmatch.py#L95). From there I learned that the generated regexp gets quite a bit more complicated if the pattern has `*` wildcards and especially if it has more than one of them. My explicit pattern `f"*{pattern}*"` guaranteed that there’s always two or more wildcards and even our original version `f"{pattern}*"` always has one.

I’m now planning to change our code to

```python
pattern = fnmatch.translate(pattern)[:-2]

```

It ought to work exactly the same way as

```python
pattern = fnmatch.translate(pattern, anchoring=False)

```

that I have proposed here, but the latter would be cleaner and wouldn’t break if someone changed the end of the pattern returned by `fnmatch.translate`. Very importantly, having this in the standard library would make the functionality easy to discover and avoid others making the same mistake I made.
