summaryrefslogtreecommitdiffstats
path: root/Lib/pathlib.py
diff options
context:
space:
mode:
authorStanislav Zmiev <szmiev2000@gmail.com>2023-03-22 14:45:25 (GMT)
committerGitHub <noreply@github.com>2023-03-22 14:45:25 (GMT)
commit713df2c53489ce8012d0ede10b70950e6b0d8372 (patch)
tree8ef14b4352a308817169ae77533537b958aedef4 /Lib/pathlib.py
parentaf9c34f6ef8dceb21871206eb3e4d350f6e3d3dc (diff)
downloadcpython-713df2c53489ce8012d0ede10b70950e6b0d8372.zip
cpython-713df2c53489ce8012d0ede10b70950e6b0d8372.tar.gz
cpython-713df2c53489ce8012d0ede10b70950e6b0d8372.tar.bz2
GH-89727: Fix pathlib.Path.walk RecursionError on deep trees (GH-100282)
Use a stack to implement `pathlib.Path.walk()` iteratively instead of recursively to avoid hitting recursion limits on deeply nested trees. Co-authored-by: Barney Gale <barney.gale@gmail.com> Co-authored-by: Brett Cannon <brett@python.org>
Diffstat (limited to 'Lib/pathlib.py')
-rw-r--r--Lib/pathlib.py78
1 files changed, 40 insertions, 38 deletions
diff --git a/Lib/pathlib.py b/Lib/pathlib.py
index 55c44f1..a126bf2 100644
--- a/Lib/pathlib.py
+++ b/Lib/pathlib.py
@@ -1197,45 +1197,47 @@ class Path(PurePath):
def walk(self, top_down=True, on_error=None, follow_symlinks=False):
"""Walk the directory tree from this directory, similar to os.walk()."""
sys.audit("pathlib.Path.walk", self, on_error, follow_symlinks)
- return self._walk(top_down, on_error, follow_symlinks)
-
- def _walk(self, top_down, on_error, follow_symlinks):
- # We may not have read permission for self, in which case we can't
- # get a list of the files the directory contains. os.walk
- # always suppressed the exception then, rather than blow up for a
- # minor reason when (say) a thousand readable directories are still
- # left to visit. That logic is copied here.
- try:
- scandir_it = self._scandir()
- except OSError as error:
- if on_error is not None:
- on_error(error)
- return
-
- with scandir_it:
- dirnames = []
- filenames = []
- for entry in scandir_it:
- try:
- is_dir = entry.is_dir(follow_symlinks=follow_symlinks)
- except OSError:
- # Carried over from os.path.isdir().
- is_dir = False
-
- if is_dir:
- dirnames.append(entry.name)
- else:
- filenames.append(entry.name)
-
- if top_down:
- yield self, dirnames, filenames
-
- for dirname in dirnames:
- dirpath = self._make_child_relpath(dirname)
- yield from dirpath._walk(top_down, on_error, follow_symlinks)
+ paths = [self]
+
+ while paths:
+ path = paths.pop()
+ if isinstance(path, tuple):
+ yield path
+ continue
+
+ # We may not have read permission for self, in which case we can't
+ # get a list of the files the directory contains. os.walk()
+ # always suppressed the exception in that instance, rather than
+ # blow up for a minor reason when (say) a thousand readable
+ # directories are still left to visit. That logic is copied here.
+ try:
+ scandir_it = path._scandir()
+ except OSError as error:
+ if on_error is not None:
+ on_error(error)
+ continue
+
+ with scandir_it:
+ dirnames = []
+ filenames = []
+ for entry in scandir_it:
+ try:
+ is_dir = entry.is_dir(follow_symlinks=follow_symlinks)
+ except OSError:
+ # Carried over from os.path.isdir().
+ is_dir = False
+
+ if is_dir:
+ dirnames.append(entry.name)
+ else:
+ filenames.append(entry.name)
+
+ if top_down:
+ yield path, dirnames, filenames
+ else:
+ paths.append((path, dirnames, filenames))
- if not top_down:
- yield self, dirnames, filenames
+ paths += [path._make_child_relpath(d) for d in reversed(dirnames)]
class PosixPath(Path, PurePosixPath):