Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-74598: add fnmatch.filterfalse for excluding names #121185

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
10 changes: 10 additions & 0 deletions Doc/library/fnmatch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ functions: :func:`fnmatch`, :func:`fnmatchcase`, :func:`.filter`.
but implemented more efficiently.


.. function:: filterfalse(names, pat)

Construct a list from those elements of the :term:`iterable` of filename
strings *names* that do not match the pattern string *pat*.
It is the same as ``[n for n in names if not fnmatch(n, pat)]``,
but implemented more efficiently.

.. versionadded:: next


.. function:: translate(pat)

Return the shell-style pattern *pat* converted to a regular expression for
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,13 @@ errno
(Contributed by James Roy in :gh:`126585`.)


fnmatch
-------

* Added :func:`fnmatch.filterfalse` for excluding names matching a pattern.
(Contributed by Bénédikt Tran in :gh:`74598`.)


fractions
---------

Expand Down
18 changes: 17 additions & 1 deletion Lib/fnmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
The function translate(PATTERN) returns a regular expression
corresponding to PATTERN. (It does not compile it.)
"""

import functools
import itertools
import os
import posixpath
import re
import functools

__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]

Expand Down Expand Up @@ -61,6 +63,20 @@ def filter(names, pat):
result.append(name)
return result

def filterfalse(names, pat):
"""Construct a list from those elements of the iterable NAMES that do not match PAT."""
pat = os.path.normcase(pat)
match = _compile_pattern(pat)
if os.path is posixpath:
# normcase on posix is NOP. Optimize it away from the loop.
return list(itertools.filterfalse(match, names))

result = []
for name in names:
if match(os.path.normcase(name)) is None:
result.append(name)
return result

def fnmatchcase(name, pat):
"""Test whether FILENAME matches PATTERN, including case.

Expand Down
8 changes: 7 additions & 1 deletion Lib/test/test_fnmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import string
import warnings

from fnmatch import fnmatch, fnmatchcase, translate, filter
from fnmatch import fnmatch, fnmatchcase, translate, filter, filterfalse

class FnmatchTestCase(unittest.TestCase):

Expand Down Expand Up @@ -327,6 +327,12 @@ def test_filter(self):
self.assertEqual(filter([b'Python', b'Ruby', b'Perl', b'Tcl'], b'P*'),
[b'Python', b'Perl'])

def test_filterfalse(self):
actual = filterfalse(['Python', 'Ruby', 'Perl', 'Tcl'], 'P*')
self.assertListEqual(actual, ['Ruby', 'Tcl'])
actual = filterfalse([b'Python', b'Ruby', b'Perl', b'Tcl'], b'P*')
self.assertListEqual(actual, [b'Ruby', b'Tcl'])

def test_mix_bytes_str(self):
self.assertRaises(TypeError, filter, ['test'], b'*')
self.assertRaises(TypeError, filter, [b'test'], '*')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :func:`fnmatch.filterfalse` for excluding names matching a pattern.
Patch by Bénédikt Tran.
Loading