summaryrefslogtreecommitdiff
path: root/Lib/os.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/os.py')
-rw-r--r--Lib/os.py150
1 files changed, 108 insertions, 42 deletions
diff --git a/Lib/os.py b/Lib/os.py
index b4c651d24d..67e1992836 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -22,7 +22,7 @@ and opendir), and leave all pathname manipulation to os.path
"""
#'
-
+import abc
import sys, errno
import stat as st
@@ -356,6 +356,7 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
dirs = []
nondirs = []
+ walk_dirs = []
# We may not have read permission for top, in which case we can't
# get a list of the files the directory contains. os.walk
@@ -369,42 +370,52 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
# Note that scandir is global in this module due
# to earlier import-*.
scandir_it = scandir(top)
- entries = list(scandir_it)
except OSError as error:
if onerror is not None:
onerror(error)
return
- for entry in entries:
- try:
- is_dir = entry.is_dir()
- except OSError:
- # If is_dir() raises an OSError, consider that the entry is not
- # a directory, same behaviour than os.path.isdir().
- is_dir = False
-
- if is_dir:
- dirs.append(entry.name)
- else:
- nondirs.append(entry.name)
+ with scandir_it:
+ while True:
+ try:
+ try:
+ entry = next(scandir_it)
+ except StopIteration:
+ break
+ except OSError as error:
+ if onerror is not None:
+ onerror(error)
+ return
- if not topdown and is_dir:
- # Bottom-up: recurse into sub-directory, but exclude symlinks to
- # directories if followlinks is False
- if followlinks:
- walk_into = True
+ try:
+ is_dir = entry.is_dir()
+ except OSError:
+ # If is_dir() raises an OSError, consider that the entry is not
+ # a directory, same behaviour than os.path.isdir().
+ is_dir = False
+
+ if is_dir:
+ dirs.append(entry.name)
else:
- try:
- is_symlink = entry.is_symlink()
- except OSError:
- # If is_symlink() raises an OSError, consider that the
- # entry is not a symbolic link, same behaviour than
- # os.path.islink().
- is_symlink = False
- walk_into = not is_symlink
+ nondirs.append(entry.name)
- if walk_into:
- yield from walk(entry.path, topdown, onerror, followlinks)
+ if not topdown and is_dir:
+ # Bottom-up: recurse into sub-directory, but exclude symlinks to
+ # directories if followlinks is False
+ if followlinks:
+ walk_into = True
+ else:
+ try:
+ is_symlink = entry.is_symlink()
+ except OSError:
+ # If is_symlink() raises an OSError, consider that the
+ # entry is not a symbolic link, same behaviour than
+ # os.path.islink().
+ is_symlink = False
+ walk_into = not is_symlink
+
+ if walk_into:
+ walk_dirs.append(entry.path)
# Yield before recursion if going top down
if topdown:
@@ -421,6 +432,9 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
if followlinks or not islink(new_path):
yield from walk(new_path, topdown, onerror, followlinks)
else:
+ # Recurse into sub-directories
+ for new_path in walk_dirs:
+ yield from walk(new_path, topdown, onerror, followlinks)
# Yield after recursion if going bottom up
yield top, dirs, nondirs
@@ -467,10 +481,23 @@ class _DummyDirEntry:
stat = self.stat(follow_symlinks=False)
return st.S_ISLNK(stat.st_mode)
-def _dummy_scandir(dir):
+class _dummy_scandir:
# listdir-based implementation for bytes patches on Windows
- for name in listdir(dir):
- yield _DummyDirEntry(dir, name)
+ def __init__(self, dir):
+ self.dir = dir
+ self.it = iter(listdir(dir))
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return _DummyDirEntry(self.dir, next(self.it))
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ self.it = iter(())
__all__.append("walk")
@@ -849,30 +876,34 @@ def _fscodec():
errors = 'surrogateescape'
def fsencode(filename):
+ """Encode filename (an os.PathLike, bytes, or str) to the filesystem
+ encoding with 'surrogateescape' error handler, return bytes unchanged.
+ On Windows, use 'strict' error handler if the file system encoding is
+ 'mbcs' (which is the default encoding).
"""
- Encode filename to the filesystem encoding with 'surrogateescape' error
- handler, return bytes unchanged. On Windows, use 'strict' error handler if
- the file system encoding is 'mbcs' (which is the default encoding).
- """
+ filename = fspath(filename)
if isinstance(filename, bytes):
return filename
elif isinstance(filename, str):
return filename.encode(encoding, errors)
else:
- raise TypeError("expect bytes or str, not %s" % type(filename).__name__)
+ raise TypeError("expected str, bytes or os.PathLike object, not "
+ + type(filename).__name__)
def fsdecode(filename):
+ """Decode filename (an os.PathLike, bytes, or str) from the filesystem
+ encoding with 'surrogateescape' error handler, return str unchanged. On
+ Windows, use 'strict' error handler if the file system encoding is
+ 'mbcs' (which is the default encoding).
"""
- Decode filename from the filesystem encoding with 'surrogateescape' error
- handler, return str unchanged. On Windows, use 'strict' error handler if
- the file system encoding is 'mbcs' (which is the default encoding).
- """
+ filename = fspath(filename)
if isinstance(filename, str):
return filename
elif isinstance(filename, bytes):
return filename.decode(encoding, errors)
else:
- raise TypeError("expect bytes or str, not %s" % type(filename).__name__)
+ raise TypeError("expected str, bytes or os.PathLike object, not "
+ + type(filename).__name__)
return fsencode, fsdecode
@@ -1070,3 +1101,38 @@ def fdopen(fd, *args, **kwargs):
raise TypeError("invalid fd type (%s, expected integer)" % type(fd))
import io
return io.open(fd, *args, **kwargs)
+
+# Supply os.fspath() if not defined in C
+if not _exists('fspath'):
+ def fspath(path):
+ """Return the string representation of the path.
+
+ If str or bytes is passed in, it is returned unchanged.
+ """
+ if isinstance(path, (str, bytes)):
+ return path
+
+ # Work from the object's type to match method resolution of other magic
+ # methods.
+ path_type = type(path)
+ try:
+ return path_type.__fspath__(path)
+ except AttributeError:
+ if hasattr(path_type, '__fspath__'):
+ raise
+
+ raise TypeError("expected str, bytes or os.PathLike object, not "
+ + path_type.__name__)
+
+class PathLike(abc.ABC):
+
+ """Abstract base class for implementing the file system path protocol."""
+
+ @abc.abstractmethod
+ def __fspath__(self):
+ """Return the file system path representation of the object."""
+ raise NotImplementedError
+
+ @classmethod
+ def __subclasshook__(cls, subclass):
+ return hasattr(subclass, '__fspath__')