From 538ab0a430119cf64c52bf2fea3e657ac7a1e954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:00:30 +0200 Subject: [PATCH 01/11] add `fnmatch.filterfalse` --- Lib/fnmatch.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index 73acb1fe8d4106..1e1080872f41ef 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -61,6 +61,23 @@ 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: + # using lambda function is usually worse than using explicit 'not' + if not match(name): + result.append(name) + else: + for name in names: + if not match(os.path.normcase(name)): + result.append(name) + return result + def fnmatchcase(name, pat): """Test whether FILENAME matches PATTERN, including case. From ce7720c9c679c2dcae4ad6b00abea8eaf3560503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:00:40 +0200 Subject: [PATCH 02/11] add tests --- Lib/test/test_fnmatch.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_fnmatch.py b/Lib/test/test_fnmatch.py index 10ed496d4e2f37..53eacefcf89a5c 100644 --- a/Lib/test/test_fnmatch.py +++ b/Lib/test/test_fnmatch.py @@ -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): @@ -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'], '*') From 11ca0f3de36ee904a742580770ab36af94f71a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:00:46 +0200 Subject: [PATCH 03/11] add doc --- Doc/library/fnmatch.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Doc/library/fnmatch.rst b/Doc/library/fnmatch.rst index fda44923f204fc..30a91bbb90a0ae 100644 --- a/Doc/library/fnmatch.rst +++ b/Doc/library/fnmatch.rst @@ -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 From c1957f2166e7cbb4e12d44ded5d2bcf389cb88c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:00:54 +0200 Subject: [PATCH 04/11] add What's New? --- Doc/whatsnew/3.14.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index ee3001661b3143..642c4c622c2628 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -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 -- From cbb7813cd7492db9afd17bcfb3cc5f899e310498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:00:58 +0200 Subject: [PATCH 05/11] blurb --- .../next/Library/2024-06-30-17-00-00.gh-issue-74598.1gVy_8.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-06-30-17-00-00.gh-issue-74598.1gVy_8.rst diff --git a/Misc/NEWS.d/next/Library/2024-06-30-17-00-00.gh-issue-74598.1gVy_8.rst b/Misc/NEWS.d/next/Library/2024-06-30-17-00-00.gh-issue-74598.1gVy_8.rst new file mode 100644 index 00000000000000..3e0d052a58219e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-06-30-17-00-00.gh-issue-74598.1gVy_8.rst @@ -0,0 +1,2 @@ +Add :func:`fnmatch.filterfalse` for excluding names matching a pattern. +Patch by Bénédikt Tran. From 56e6a0ec9122264f7eda09a1678feb5b0e672eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 30 Jun 2024 17:07:23 +0200 Subject: [PATCH 06/11] use 'is None' instead of 'not' --- Lib/fnmatch.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index 1e1080872f41ef..6fc4aa4199715d 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -69,12 +69,11 @@ def filterfalse(names, pat): if os.path is posixpath: # normcase on posix is NOP. Optimize it away from the loop. for name in names: - # using lambda function is usually worse than using explicit 'not' - if not match(name): + if match(name) is None: result.append(name) else: for name in names: - if not match(os.path.normcase(name)): + if match(os.path.normcase(name)) is None: result.append(name) return result From f36b2a8a1fb96de78a8425238dc1841f45674491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:00:07 +0200 Subject: [PATCH 07/11] use `itertools.filterfalse` with POSIX paths --- Lib/fnmatch.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index 6fc4aa4199715d..006107a07b64cf 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -9,6 +9,7 @@ The function translate(PATTERN) returns a regular expression corresponding to PATTERN. (It does not compile it.) """ +import itertools import os import posixpath import re @@ -68,9 +69,7 @@ def filterfalse(names, 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) + return list(itertools.filterfalse(match, names)) else: for name in names: if match(os.path.normcase(name)) is None: From 0030a5e0b7600cb7972aed4c4bce04865fd0fbe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:00:33 +0200 Subject: [PATCH 08/11] re-order imports --- Lib/fnmatch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index 006107a07b64cf..b9481cb1a2583b 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -9,11 +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"] From ddeea64d77ed35acc87a3f075963dbe49810b2bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:01:11 +0200 Subject: [PATCH 09/11] micro-optimization of variable --- Lib/fnmatch.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py index b9481cb1a2583b..be3cc7751b972c 100644 --- a/Lib/fnmatch.py +++ b/Lib/fnmatch.py @@ -65,16 +65,16 @@ def filter(names, pat): 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. return list(itertools.filterfalse(match, names)) - else: - for name in names: - if match(os.path.normcase(name)) is None: - result.append(name) + + result = [] + for name in names: + if match(os.path.normcase(name)) is None: + result.append(name) return result def fnmatchcase(name, pat): From 02b422cfa2159737ddcf20371e3ade6631174018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 15 Dec 2024 18:20:14 +0100 Subject: [PATCH 10/11] Update Doc/whatsnew/3.14.rst --- Doc/whatsnew/3.14.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 3a8ea8adec4546..4aa9be54dec455 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -300,6 +300,13 @@ dis (Contributed by Bénédikt Tran in :gh:`123165`.) +fnmatch +------- + +* Added :func:`fnmatch.filterfalse` for excluding names matching a pattern. + (Contributed by Bénédikt Tran in :gh:`74598`.) + + fractions --------- @@ -424,13 +431,6 @@ operator (Contributed by Raymond Hettinger and Nico Mexis in :gh:`115808`.) -fnmatch -------- - -* Added :func:`fnmatch.filterfalse` for excluding names matching a pattern. - - (Contributed by Bénédikt Tran in :gh:`74598`.) - os -- From bc1a3c71ed5fc74ce398f9edf30d8bc443b0dda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 15 Dec 2024 18:21:32 +0100 Subject: [PATCH 11/11] Update Doc/library/fnmatch.rst --- Doc/library/fnmatch.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/fnmatch.rst b/Doc/library/fnmatch.rst index 30a91bbb90a0ae..b6ea66357f6a48 100644 --- a/Doc/library/fnmatch.rst +++ b/Doc/library/fnmatch.rst @@ -91,7 +91,7 @@ cache the compiled regex patterns in the following functions: :func:`fnmatch`, It is the same as ``[n for n in names if not fnmatch(n, pat)]``, but implemented more efficiently. - .. versionadded:: 3.14 + .. versionadded:: next .. function:: translate(pat)