Mailing List Archive

gh-117349: Micro-optimize a few `os.path` functions (#117350)
https://github.com/python/cpython/commit/cae4cdd07ddfcd8bcc05d683bac53815391c9907
commit: cae4cdd07ddfcd8bcc05d683bac53815391c9907
branch: main
author: Nice Zombies <nineteendo19d0@gmail.com>
committer: AlexWaygood <Alex.Waygood@Gmail.com>
date: 2024-04-02T21:32:35+01:00
summary:

gh-117349: Micro-optimize a few `os.path` functions (#117350)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Barney Gale <barney.gale@gmail.com>
Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst
M Lib/ntpath.py
M Lib/posixpath.py
M Misc/ACKS

diff --git a/Lib/ntpath.py b/Lib/ntpath.py
index ecfc7d48dbb192..0650f14f89f10b 100644
--- a/Lib/ntpath.py
+++ b/Lib/ntpath.py
@@ -102,11 +102,11 @@ def join(path, *paths):
if isinstance(path, bytes):
sep = b'\\'
seps = b'\\/'
- colon = b':'
+ colon_seps = b':\\/'
else:
sep = '\\'
seps = '\\/'
- colon = ':'
+ colon_seps = ':\\/'
try:
if not paths:
path[:0] + sep #23780: Ensure compatible data type even if p is null.
@@ -135,7 +135,7 @@ def join(path, *paths):
result_path = result_path + p_path
## add separator between UNC and non-absolute path
if (result_path and not result_root and
- result_drive and result_drive[-1:] not in colon + seps):
+ result_drive and result_drive[-1] not in colon_seps):
return result_drive + sep + result_path
return result_drive + result_root + result_path
except (TypeError, AttributeError, BytesWarning):
@@ -279,7 +279,7 @@ def isjunction(path):
st = os.lstat(path)
except (OSError, ValueError, AttributeError):
return False
- return bool(st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT)
+ return st.st_reparse_tag == stat.IO_REPARSE_TAG_MOUNT_POINT
else:
# Use genericpath.isjunction as imported above
pass
@@ -340,8 +340,8 @@ def isreserved(path):
def _isreservedname(name):
"""Return true if the filename is reserved by the system."""
# Trailing dots and spaces are reserved.
- if name.endswith(('.', ' ')) and name not in ('.', '..'):
- return True
+ if name[-1:] in ('.', ' '):
+ return name not in ('.', '..')
# Wildcards, separators, colon, and pipe (*?"<>/\:|) are reserved.
# ASCII control characters (0-31) are reserved.
# Colon is reserved for file streams (e.g. "name:stream[:type]").
@@ -350,9 +350,7 @@ def _isreservedname(name):
# DOS device names are reserved (e.g. "nul" or "nul .txt"). The rules
# are complex and vary across Windows versions. On the side of
# caution, return True for names that may not be reserved.
- if name.partition('.')[0].rstrip(' ').upper() in _reserved_names:
- return True
- return False
+ return name.partition('.')[0].rstrip(' ').upper() in _reserved_names


# Expand paths beginning with '~' or '~user'.
@@ -381,13 +379,10 @@ def expanduser(path):

if 'USERPROFILE' in os.environ:
userhome = os.environ['USERPROFILE']
- elif not 'HOMEPATH' in os.environ:
+ elif 'HOMEPATH' not in os.environ:
return path
else:
- try:
- drive = os.environ['HOMEDRIVE']
- except KeyError:
- drive = ''
+ drive = os.environ.get('HOMEDRIVE', '')
userhome = join(drive, os.environ['HOMEPATH'])

if i != 1: #~user
@@ -727,7 +722,8 @@ def realpath(path, *, strict=False):
new_unc_prefix = b'\\\\'
cwd = os.getcwdb()
# bpo-38081: Special case for realpath(b'nul')
- if normcase(path) == normcase(os.fsencode(devnull)):
+ devnull = b'nul'
+ if normcase(path) == devnull:
return b'\\\\.\\NUL'
else:
prefix = '\\\\?\\'
@@ -735,7 +731,8 @@ def realpath(path, *, strict=False):
new_unc_prefix = '\\\\'
cwd = os.getcwd()
# bpo-38081: Special case for realpath('nul')
- if normcase(path) == normcase(devnull):
+ devnull = 'nul'
+ if normcase(path) == devnull:
return '\\\\.\\NUL'
had_prefix = path.startswith(prefix)
if not had_prefix and not isabs(path):
diff --git a/Lib/posixpath.py b/Lib/posixpath.py
index 4fc02be69bd6e1..76ee721bfb5e33 100644
--- a/Lib/posixpath.py
+++ b/Lib/posixpath.py
@@ -213,15 +213,8 @@ def ismount(path):
except (OSError, ValueError):
return False

- dev1 = s1.st_dev
- dev2 = s2.st_dev
- if dev1 != dev2:
- return True # path/.. on a different device as path
- ino1 = s1.st_ino
- ino2 = s2.st_ino
- if ino1 == ino2:
- return True # path/.. is the same i-node as path
- return False
+ # path/.. on a different device as path or the same i-node as path
+ return s1.st_dev != s2.st_dev or s1.st_ino == s2.st_ino


# Expand paths beginning with '~' or '~user'.
@@ -270,7 +263,7 @@ def expanduser(path):
return path
name = path[1:i]
if isinstance(name, bytes):
- name = str(name, 'ASCII')
+ name = name.decode('ascii')
try:
pwent = pwd.getpwnam(name)
except KeyError:
@@ -359,21 +352,19 @@ def normpath(path):
path = os.fspath(path)
if isinstance(path, bytes):
sep = b'/'
- empty = b''
dot = b'.'
dotdot = b'..'
else:
sep = '/'
- empty = ''
dot = '.'
dotdot = '..'
- if path == empty:
+ if not path:
return dot
_, initial_slashes, path = splitroot(path)
comps = path.split(sep)
new_comps = []
for comp in comps:
- if comp in (empty, dot):
+ if not comp or comp == dot:
continue
if (comp != dotdot or (not initial_slashes and not new_comps) or
(new_comps and new_comps[-1] == dotdot)):
@@ -396,12 +387,12 @@ def normpath(path):
def abspath(path):
"""Return an absolute path."""
path = os.fspath(path)
- if not isabs(path):
- if isinstance(path, bytes):
- cwd = os.getcwdb()
- else:
- cwd = os.getcwd()
- path = join(cwd, path)
+ if isinstance(path, bytes):
+ if not path.startswith(b'/'):
+ path = join(os.getcwdb(), path)
+ else:
+ if not path.startswith('/'):
+ path = join(os.getcwd(), path)
return normpath(path)


@@ -417,6 +408,7 @@ def realpath(filename, *, strict=False):

# Join two paths, normalizing and eliminating any symbolic links
# encountered in the second path.
+# Two leading slashes are replaced by a single slash.
def _joinrealpath(path, rest, strict, seen):
if isinstance(path, bytes):
sep = b'/'
@@ -427,7 +419,7 @@ def _joinrealpath(path, rest, strict, seen):
curdir = '.'
pardir = '..'

- if isabs(rest):
+ if rest.startswith(sep):
rest = rest[1:]
path = sep

@@ -439,10 +431,15 @@ def _joinrealpath(path, rest, strict, seen):
if name == pardir:
# parent dir
if path:
- path, name = split(path)
+ parent, name = split(path)
if name == pardir:
- path = join(path, pardir, pardir)
+ # ../..
+ path = join(path, pardir)
+ else:
+ # foo/bar/.. -> foo
+ path = parent
else:
+ # ..
path = pardir
continue
newpath = join(path, name)
diff --git a/Misc/ACKS b/Misc/ACKS
index 03e458d170d2b8..fe014a364dd82d 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -191,6 +191,7 @@ Finn Bock
Paul Boddie
Matthew Boedicker
Robin Boerdijk
+Wannes Boeykens
Andra Bogildea
Matt Bogosian
Nikolay Bogoychev
diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst
new file mode 100644
index 00000000000000..7a7bc689002017
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2024-03-29-15-04-13.gh-issue-117349.OB9kQQ.rst
@@ -0,0 +1 @@
+Optimise several functions in :mod:`os.path`.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-leave@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: list-python-checkins@lists.gossamer-threads.com