Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 86ebd5c

Browse filesBrowse files
mdboomeryksun
andauthored
gh-101196: Make isdir/isfile/exists faster on Windows (GH-101324)
Co-authored-by: Eryk Sun <eryksun@gmail.com>
1 parent 3a88de7 commit 86ebd5c
Copy full SHA for 86ebd5c

File tree

Expand file treeCollapse file tree

9 files changed

+624
-34
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+624
-34
lines changed

‎Include/pyport.h

Copy file name to clipboardExpand all lines: Include/pyport.h
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ typedef Py_ssize_t Py_ssize_clean_t;
247247
#define S_ISCHR(x) (((x) & S_IFMT) == S_IFCHR)
248248
#endif
249249

250+
#ifndef S_ISLNK
251+
#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK)
252+
#endif
253+
250254
#ifdef __cplusplus
251255
/* Move this down here since some C++ #include's don't like to be included
252256
inside an extern "C" */

‎Lib/genericpath.py

Copy file name to clipboardExpand all lines: Lib/genericpath.py
+13-1Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import stat
88

99
__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
10-
'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile',
10+
'getsize', 'isdir', 'isfile', 'islink', 'samefile', 'sameopenfile',
1111
'samestat']
1212

1313

@@ -45,6 +45,18 @@ def isdir(s):
4545
return stat.S_ISDIR(st.st_mode)
4646

4747

48+
# Is a path a symbolic link?
49+
# This will always return false on systems where os.lstat doesn't exist.
50+
51+
def islink(path):
52+
"""Test whether a path is a symbolic link"""
53+
try:
54+
st = os.lstat(path)
55+
except (OSError, ValueError, AttributeError):
56+
return False
57+
return stat.S_ISLNK(st.st_mode)
58+
59+
4860
def getsize(filename):
4961
"""Return the size of a file, reported by os.stat()."""
5062
return os.stat(filename).st_size

‎Lib/ntpath.py

Copy file name to clipboardExpand all lines: Lib/ntpath.py
+8-19Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -276,19 +276,6 @@ def dirname(p):
276276
"""Returns the directory component of a pathname"""
277277
return split(p)[0]
278278

279-
# Is a path a symbolic link?
280-
# This will always return false on systems where os.lstat doesn't exist.
281-
282-
def islink(path):
283-
"""Test whether a path is a symbolic link.
284-
This will always return false for Windows prior to 6.0.
285-
"""
286-
try:
287-
st = os.lstat(path)
288-
except (OSError, ValueError, AttributeError):
289-
return False
290-
return stat.S_ISLNK(st.st_mode)
291-
292279

293280
# Is a path a junction?
294281

@@ -870,11 +857,13 @@ def commonpath(paths):
870857

871858

872859
try:
873-
# The genericpath.isdir implementation uses os.stat and checks the mode
874-
# attribute to tell whether or not the path is a directory.
875-
# This is overkill on Windows - just pass the path to GetFileAttributes
876-
# and check the attribute from there.
877-
from nt import _isdir as isdir
860+
# The isdir(), isfile(), islink() and exists() implementations in
861+
# genericpath use os.stat(). This is overkill on Windows. Use simpler
862+
# builtin functions if they are available.
863+
from nt import _path_isdir as isdir
864+
from nt import _path_isfile as isfile
865+
from nt import _path_islink as islink
866+
from nt import _path_exists as exists
878867
except ImportError:
879-
# Use genericpath.isdir as imported above.
868+
# Use genericpath.* as imported above
880869
pass

‎Lib/posixpath.py

Copy file name to clipboardExpand all lines: Lib/posixpath.py
-12Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,6 @@ def dirname(p):
187187
return head
188188

189189

190-
# Is a path a symbolic link?
191-
# This will always return false on systems where os.lstat doesn't exist.
192-
193-
def islink(path):
194-
"""Test whether a path is a symbolic link"""
195-
try:
196-
st = os.lstat(path)
197-
except (OSError, ValueError, AttributeError):
198-
return False
199-
return stat.S_ISLNK(st.st_mode)
200-
201-
202190
# Is a path a junction?
203191

204192
def isjunction(path):

‎Lib/test/test_ntpath.py

Copy file name to clipboardExpand all lines: Lib/test/test_ntpath.py
+31-1Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import inspect
12
import ntpath
23
import os
34
import sys
45
import unittest
56
import warnings
6-
from test.support import os_helper
7+
from test.support import cpython_only, os_helper
78
from test.support import TestFailed, is_emscripten
89
from test.support.os_helper import FakePath
910
from test import test_genericpath
@@ -938,6 +939,35 @@ def test_isjunction(self):
938939
self.assertFalse(ntpath.isjunction('tmpdir'))
939940
self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir'))
940941

942+
@unittest.skipIf(sys.platform != 'win32', "drive letters are a windows concept")
943+
def test_isfile_driveletter(self):
944+
drive = os.environ.get('SystemDrive')
945+
if drive is None or len(drive) != 2 or drive[1] != ':':
946+
raise unittest.SkipTest('SystemDrive is not defined or malformed')
947+
self.assertFalse(os.path.isfile('\\\\.\\' + drive))
948+
949+
@unittest.skipIf(sys.platform != 'win32', "windows only")
950+
def test_con_device(self):
951+
self.assertFalse(os.path.isfile(r"\\.\CON"))
952+
self.assertFalse(os.path.isdir(r"\\.\CON"))
953+
self.assertFalse(os.path.islink(r"\\.\CON"))
954+
self.assertTrue(os.path.exists(r"\\.\CON"))
955+
956+
@unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32")
957+
@cpython_only
958+
def test_fast_paths_in_use(self):
959+
# There are fast paths of these functions implemented in posixmodule.c.
960+
# Confirm that they are being used, and not the Python fallbacks in
961+
# genericpath.py.
962+
self.assertTrue(os.path.isdir is nt._path_isdir)
963+
self.assertFalse(inspect.isfunction(os.path.isdir))
964+
self.assertTrue(os.path.isfile is nt._path_isfile)
965+
self.assertFalse(inspect.isfunction(os.path.isfile))
966+
self.assertTrue(os.path.islink is nt._path_islink)
967+
self.assertFalse(inspect.isfunction(os.path.islink))
968+
self.assertTrue(os.path.exists is nt._path_exists)
969+
self.assertFalse(inspect.isfunction(os.path.exists))
970+
941971

942972
class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase):
943973
pathmodule = ntpath

‎Lib/test/test_os.py

Copy file name to clipboardExpand all lines: Lib/test/test_os.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,7 @@ def test_access_denied(self):
742742
)
743743
result = os.stat(fname)
744744
self.assertNotEqual(result.st_size, 0)
745+
self.assertTrue(os.path.isfile(fname))
745746

746747
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
747748
def test_stat_block_device(self):
@@ -2860,6 +2861,7 @@ def test_appexeclink(self):
28602861
self.assertEqual(st, os.stat(alias))
28612862
self.assertFalse(stat.S_ISLNK(st.st_mode))
28622863
self.assertEqual(st.st_reparse_tag, stat.IO_REPARSE_TAG_APPEXECLINK)
2864+
self.assertTrue(os.path.isfile(alias))
28632865
# testing the first one we see is sufficient
28642866
break
28652867
else:
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The functions ``os.path.isdir``, ``os.path.isfile``, ``os.path.islink`` and
2+
``os.path.exists`` are now 13% to 28% faster on Windows, by making fewer Win32
3+
API calls.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.