Skip to content

Commit 47770a1

Browse files
authored
GH-104104: Optimize pathlib.Path.glob() by avoiding repeated calls to os.path.normcase() (GH-104105)
Use `re.IGNORECASE` to implement case-insensitive matching. This restores behaviour from before GH-31691.
1 parent 1f53844 commit 47770a1

File tree

2 files changed

+16
-11
lines changed

2 files changed

+16
-11
lines changed

Lib/pathlib.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def _is_wildcard_pattern(pat):
5959
# be looked up directly as a file.
6060
return "*" in pat or "?" in pat or "[" in pat
6161

62+
def _is_case_sensitive(flavour):
63+
return flavour.normcase('Aa') == 'Aa'
64+
6265
#
6366
# Globbing helpers
6467
#
@@ -100,15 +103,14 @@ def select_from(self, parent_path):
100103
is_dir = path_cls.is_dir
101104
exists = path_cls.exists
102105
scandir = path_cls._scandir
103-
normcase = path_cls._flavour.normcase
104106
if not is_dir(parent_path):
105107
return iter([])
106-
return self._select_from(parent_path, is_dir, exists, scandir, normcase)
108+
return self._select_from(parent_path, is_dir, exists, scandir)
107109

108110

109111
class _TerminatingSelector:
110112

111-
def _select_from(self, parent_path, is_dir, exists, scandir, normcase):
113+
def _select_from(self, parent_path, is_dir, exists, scandir):
112114
yield parent_path
113115

114116

@@ -118,11 +120,11 @@ def __init__(self, name, child_parts, flavour):
118120
self.name = name
119121
_Selector.__init__(self, child_parts, flavour)
120122

121-
def _select_from(self, parent_path, is_dir, exists, scandir, normcase):
123+
def _select_from(self, parent_path, is_dir, exists, scandir):
122124
try:
123125
path = parent_path._make_child_relpath(self.name)
124126
if (is_dir if self.dironly else exists)(path):
125-
for p in self.successor._select_from(path, is_dir, exists, scandir, normcase):
127+
for p in self.successor._select_from(path, is_dir, exists, scandir):
126128
yield p
127129
except PermissionError:
128130
return
@@ -131,10 +133,11 @@ def _select_from(self, parent_path, is_dir, exists, scandir, normcase):
131133
class _WildcardSelector(_Selector):
132134

133135
def __init__(self, pat, child_parts, flavour):
134-
self.match = re.compile(fnmatch.translate(flavour.normcase(pat))).fullmatch
136+
flags = re.NOFLAG if _is_case_sensitive(flavour) else re.IGNORECASE
137+
self.match = re.compile(fnmatch.translate(pat), flags=flags).fullmatch
135138
_Selector.__init__(self, child_parts, flavour)
136139

137-
def _select_from(self, parent_path, is_dir, exists, scandir, normcase):
140+
def _select_from(self, parent_path, is_dir, exists, scandir):
138141
try:
139142
# We must close the scandir() object before proceeding to
140143
# avoid exhausting file descriptors when globbing deep trees.
@@ -153,9 +156,9 @@ def _select_from(self, parent_path, is_dir, exists, scandir, normcase):
153156
raise
154157
continue
155158
name = entry.name
156-
if self.match(normcase(name)):
159+
if self.match(name):
157160
path = parent_path._make_child_relpath(name)
158-
for p in self.successor._select_from(path, is_dir, exists, scandir, normcase):
161+
for p in self.successor._select_from(path, is_dir, exists, scandir):
159162
yield p
160163
except PermissionError:
161164
return
@@ -187,13 +190,13 @@ def _iterate_directories(self, parent_path, is_dir, scandir):
187190
except PermissionError:
188191
return
189192

190-
def _select_from(self, parent_path, is_dir, exists, scandir, normcase):
193+
def _select_from(self, parent_path, is_dir, exists, scandir):
191194
try:
192195
yielded = set()
193196
try:
194197
successor_select = self.successor._select_from
195198
for starting_point in self._iterate_directories(parent_path, is_dir, scandir):
196-
for p in successor_select(starting_point, is_dir, exists, scandir, normcase):
199+
for p in successor_select(starting_point, is_dir, exists, scandir):
197200
if p not in yielded:
198201
yield p
199202
yielded.add(p)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve performance of :meth:`pathlib.Path.glob` by using
2+
:data:`re.IGNORECASE` to implement case-insensitive matching.

0 commit comments

Comments
 (0)