diff --git a/Doc/library/fnmatch.rst b/Doc/library/fnmatch.rst index c03a9d3..373399e 100644 --- a/Doc/library/fnmatch.rst +++ b/Doc/library/fnmatch.rst @@ -71,6 +71,12 @@ patterns. Return the subset of the list of *names* that match *pattern*. It is the same as ``[n for n in names if fnmatch(n, pattern)]``, but implemented more efficiently. +.. function:: filterfalse(names, pattern) + + Return the subset of the list of *names* that don't match *pattern*. It is the + same as ``[n for n in names if not fnmatch(n, pattern)]``, but implemented more + efficiently. + .. function:: translate(pattern) diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index fd3b514..032ba0f 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -14,7 +14,10 @@ import posixpath import re import functools -__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"] +__all__ = ["filter", "filterfalse", "fnmatch", "fnmatchcase", "translate"] + +# os.path.normcase on posix is NOP. Avoid calling it unnecessarily. +normalize_case = os.path is not posixpath def fnmatch(name, pat): """Test whether FILENAME matches PATTERN. @@ -31,8 +34,9 @@ def fnmatch(name, pat): if the operating system requires it. If you don't want this, use fnmatchcase(FILENAME, PATTERN). """ - name = os.path.normcase(name) - pat = os.path.normcase(pat) + if normalize_case: + name = os.path.normcase(name) + pat = os.path.normcase(pat) return fnmatchcase(name, pat) @functools.lru_cache(maxsize=256, typed=True) @@ -47,19 +51,19 @@ def _compile_pattern(pat): def filter(names, pat): """Return the subset of the list NAMES that match PAT.""" - result = [] - pat = os.path.normcase(pat) + if normalize_case: + pat = os.path.normcase(pat) + names = (os.path.normcase(name) for name in names) 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): - result.append(name) - else: - for name in names: - if match(os.path.normcase(name)): - result.append(name) - return result + return [name for name in names if match(name)] + +def filterfalse(names, pat): + """Return the subset of the list NAMES that don't match PAT.""" + if normalize_case: + pat = os.path.normcase(pat) + names = (os.path.normcase(name) for name in names) + match = _compile_pattern(pat) + return [name for name in names if match(name) is None] def fnmatchcase(name, pat): """Test whether FILENAME matches PATTERN, including case. diff --git a/Lib/test/test_fnmatch.py b/Lib/test/test_fnmatch.py index fb74246..93c83db 100644 --- a/Lib/test/test_fnmatch.py +++ b/Lib/test/test_fnmatch.py @@ -2,7 +2,7 @@ import unittest -from fnmatch import fnmatch, fnmatchcase, translate, filter +from fnmatch import fnmatch, fnmatchcase, translate, filter, filterfalse class FnmatchTestCase(unittest.TestCase): @@ -78,5 +78,11 @@ class FilterTestCase(unittest.TestCase): self.assertEqual(filter(['a', 'b'], 'a'), ['a']) +class FilterFalseTestCase(unittest.TestCase): + + def test_filterfalse(self): + self.assertEqual(filterfalse(['a', 'b'], 'a'), ['b']) + + if __name__ == "__main__": unittest.main()