Skip to content

Update os.py from 3.13.5 #6076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 97 additions & 43 deletions Lib/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def _add(str, fn):
_add("HAVE_FCHMODAT", "chmod")
_add("HAVE_FCHOWNAT", "chown")
_add("HAVE_FSTATAT", "stat")
_add("HAVE_LSTAT", "lstat")
_add("HAVE_FUTIMESAT", "utime")
_add("HAVE_LINKAT", "link")
_add("HAVE_MKDIRAT", "mkdir")
Expand All @@ -131,6 +132,7 @@ def _add(str, fn):
_set = set()
_add("HAVE_FCHDIR", "chdir")
_add("HAVE_FCHMOD", "chmod")
_add("MS_WINDOWS", "chmod")
_add("HAVE_FCHOWN", "chown")
_add("HAVE_FDOPENDIR", "listdir")
_add("HAVE_FDOPENDIR", "scandir")
Expand Down Expand Up @@ -171,6 +173,7 @@ def _add(str, fn):
_add("HAVE_FSTATAT", "stat")
_add("HAVE_LCHFLAGS", "chflags")
_add("HAVE_LCHMOD", "chmod")
_add("MS_WINDOWS", "chmod")
if _exists("lchown"): # mac os x10.3
_add("HAVE_LCHOWN", "chown")
_add("HAVE_LINKAT", "link")
Expand Down Expand Up @@ -279,6 +282,10 @@ def renames(old, new):

__all__.extend(["makedirs", "removedirs", "renames"])

# Private sentinel that makes walk() classify all symlinks and junctions as
# regular files.
_walk_symlinks_as_files = object()

def walk(top, topdown=True, onerror=None, followlinks=False):
"""Directory tree generator.

Expand Down Expand Up @@ -331,12 +338,12 @@ def walk(top, topdown=True, onerror=None, followlinks=False):

import os
from os.path import join, getsize
for root, dirs, files in os.walk('python/Lib/email'):
for root, dirs, files in os.walk('python/Lib/xml'):
print(root, "consumes ")
print(sum(getsize(join(root, name)) for name in files), end=" ")
print("bytes in", len(files), "non-directory files")
if 'CVS' in dirs:
dirs.remove('CVS') # don't visit CVS directories
if '__pycache__' in dirs:
dirs.remove('__pycache__') # don't visit __pycache__ directories

"""
sys.audit("os.walk", top, topdown, onerror, followlinks)
Expand Down Expand Up @@ -380,7 +387,10 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
break

try:
is_dir = entry.is_dir()
if followlinks is _walk_symlinks_as_files:
is_dir = entry.is_dir(follow_symlinks=False) and not entry.is_junction()
else:
is_dir = entry.is_dir()
except OSError:
# If is_dir() raises an OSError, consider the entry not to
# be a directory, same behaviour as os.path.isdir().
Expand Down Expand Up @@ -459,34 +469,69 @@ def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=
Example:

import os
for root, dirs, files, rootfd in os.fwalk('python/Lib/email'):
for root, dirs, files, rootfd in os.fwalk('python/Lib/xml'):
print(root, "consumes", end="")
print(sum(os.stat(name, dir_fd=rootfd).st_size for name in files),
end="")
print("bytes in", len(files), "non-directory files")
if 'CVS' in dirs:
dirs.remove('CVS') # don't visit CVS directories
if '__pycache__' in dirs:
dirs.remove('__pycache__') # don't visit __pycache__ directories
"""
sys.audit("os.fwalk", top, topdown, onerror, follow_symlinks, dir_fd)
top = fspath(top)
# Note: To guard against symlink races, we use the standard
# lstat()/open()/fstat() trick.
if not follow_symlinks:
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
topfd = open(top, O_RDONLY | O_NONBLOCK, dir_fd=dir_fd)
stack = [(_fwalk_walk, (True, dir_fd, top, top, None))]
isbytes = isinstance(top, bytes)
try:
if (follow_symlinks or (st.S_ISDIR(orig_st.st_mode) and
path.samestat(orig_st, stat(topfd)))):
yield from _fwalk(topfd, top, isinstance(top, bytes),
topdown, onerror, follow_symlinks)
while stack:
yield from _fwalk(stack, isbytes, topdown, onerror, follow_symlinks)
finally:
close(topfd)

def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks):
# Close any file descriptors still on the stack.
while stack:
action, value = stack.pop()
if action == _fwalk_close:
close(value)

# Each item in the _fwalk() stack is a pair (action, args).
_fwalk_walk = 0 # args: (isroot, dirfd, toppath, topname, entry)
_fwalk_yield = 1 # args: (toppath, dirnames, filenames, topfd)
_fwalk_close = 2 # args: dirfd

def _fwalk(stack, isbytes, topdown, onerror, follow_symlinks):
# Note: This uses O(depth of the directory tree) file descriptors: if
# necessary, it can be adapted to only require O(1) FDs, see issue
# #13734.

action, value = stack.pop()
if action == _fwalk_close:
close(value)
return
elif action == _fwalk_yield:
yield value
return
assert action == _fwalk_walk
isroot, dirfd, toppath, topname, entry = value
try:
if not follow_symlinks:
# Note: To guard against symlink races, we use the standard
# lstat()/open()/fstat() trick.
if entry is None:
orig_st = stat(topname, follow_symlinks=False, dir_fd=dirfd)
else:
orig_st = entry.stat(follow_symlinks=False)
topfd = open(topname, O_RDONLY | O_NONBLOCK, dir_fd=dirfd)
except OSError as err:
if isroot:
raise
if onerror is not None:
onerror(err)
return
stack.append((_fwalk_close, topfd))
if not follow_symlinks:
if isroot and not st.S_ISDIR(orig_st.st_mode):
return
if not path.samestat(orig_st, stat(topfd)):
return

scandir_it = scandir(topfd)
dirs = []
nondirs = []
Expand All @@ -512,31 +557,18 @@ def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks):

if topdown:
yield toppath, dirs, nondirs, topfd
else:
stack.append((_fwalk_yield, (toppath, dirs, nondirs, topfd)))

for name in dirs if entries is None else zip(dirs, entries):
try:
if not follow_symlinks:
if topdown:
orig_st = stat(name, dir_fd=topfd, follow_symlinks=False)
else:
assert entries is not None
name, entry = name
orig_st = entry.stat(follow_symlinks=False)
dirfd = open(name, O_RDONLY | O_NONBLOCK, dir_fd=topfd)
except OSError as err:
if onerror is not None:
onerror(err)
continue
try:
if follow_symlinks or path.samestat(orig_st, stat(dirfd)):
dirpath = path.join(toppath, name)
yield from _fwalk(dirfd, dirpath, isbytes,
topdown, onerror, follow_symlinks)
finally:
close(dirfd)

if not topdown:
yield toppath, dirs, nondirs, topfd
toppath = path.join(toppath, toppath[:0]) # Add trailing slash.
if entries is None:
stack.extend(
(_fwalk_walk, (False, topfd, toppath + name, name, None))
for name in dirs[::-1])
else:
stack.extend(
(_fwalk_walk, (False, topfd, toppath + name, name, entry))
for name, entry in zip(dirs[::-1], entries[::-1]))

__all__.append("fwalk")

Expand Down Expand Up @@ -1061,6 +1093,12 @@ def _fspath(path):
else:
raise TypeError("expected str, bytes or os.PathLike object, "
"not " + path_type.__name__)
except TypeError:
if path_type.__fspath__ is None:
raise TypeError("expected str, bytes or os.PathLike object, "
"not " + path_type.__name__) from None
else:
raise
if isinstance(path_repr, (str, bytes)):
return path_repr
else:
Expand All @@ -1079,6 +1117,8 @@ class PathLike(abc.ABC):

"""Abstract base class for implementing the file system path protocol."""

__slots__ = ()

@abc.abstractmethod
def __fspath__(self):
"""Return the file system path representation of the object."""
Expand Down Expand Up @@ -1128,3 +1168,17 @@ def add_dll_directory(path):
cookie,
nt._remove_dll_directory
)


if _exists('sched_getaffinity') and sys._get_cpu_count_config() < 0:
def process_cpu_count():
"""
Get the number of CPUs of the current process.

Return the number of logical CPUs usable by the calling thread of the
current process. Return None if indeterminable.
"""
return len(sched_getaffinity(0))
else:
# Just an alias to cpu_count() (same docstring)
process_cpu_count = cpu_count
Loading
Loading