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
12 changes: 11 additions & 1 deletion Doc/library/fnmatch.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,21 @@ cache the compiled regex patterns in the following functions: :func:`fnmatch`,
.. function:: filter(names, pat)

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


.. function:: filterfalse(names, pat)

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

.. versionadded:: 3.14


.. 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 @@ -92,6 +92,13 @@ ast
Added :func:`ast.compare` for comparing two ASTs.
(Contributed by Batuhan Taskaya and Jeremy Hylton in :issue:`15987`.)

fnmatch
-------

* Added :func:`fnmatch.filterfalse` for excluding names matching a pattern.

(Contributed by Bénédikt Tran in :gh:`74598`.)

os
--

Expand Down
16 changes: 16 additions & 0 deletions Lib/fnmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ 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."""
result = []
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.
for name in names:
if match(name) is None:
result.append(name)
else:
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 @@ -258,6 +258,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