-
-
Notifications
You must be signed in to change notification settings - Fork 31.3k
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
ntpath.normpath('\\\\') produces different result on Windows #96290
Comments
Is this an edge case? Or an example of a more general issue? It's not an interesting path if it's only this case, so may as well special-case it. It may also be because of some code to skip |
I think we care about unnecessary regressions, but can't promise I'll get time myself to address it for 3.11. |
Of course, no worries. My PR targets In the old implementation, leading slashes are collapsed unless a UNC path is supplied. >>> ntpath.normpath('\\\\foo\\bar')
'\\\\foo\\bar'
>>> ntpath.normpath('\\\\foo\\\\')
'\\foo'
>>> ntpath.normpath('\\\\foo\\')
'\\\\foo\\'
>>> ntpath.normpath('\\\\foo')
'\\foo'
>>> ntpath.normpath('\\\\')
'\\' The new implementation returns the input unchanged in each case above. |
The pure-Python implementation of
This behavior is consistent with the system's >>> nt._path_splitroot('//')
('//', '')
>>> nt._path_splitroot('///')
('///', '')
>>> nt._path_splitroot('///y')
('///y', '')
>>> nt._path_splitroot('//x')
('//x', '')
>>> nt._path_splitroot('//x/')
('//x/', '')
>>> print(nt._getfullpathname('//..'))
\\
>>> print(nt._getfullpathname('///..'))
\\\
>>> print(nt._getfullpathname('//x/..'))
\\x\
>>> print(nt._getfullpathname('//x//..'))
\\x\
>>> print(nt._getfullpathname('//x/y/..'))
\\x\y
>>> print(nt._getfullpathname('//x/./..'))
\\x\
>>> print(nt._getfullpathname('//x/../..'))
\\x\ Unfortunately, >>> print(nt._getfullpathname('//?/C:/..'))
\\?\
>>> print(nt._getfullpathname('//./C:/..'))
\\.\ The C++
That said, I think the extended drive concept is more useful and worth the minor inconsistency with >>> nt._path_normpath('//./pipe/..')
'\\\\.\\pipe\\'
>>> nt._path_normpath('//?/C:/..')
'\\\\?\\C:\\'
>>> nt._path_normpath('//server/share/..')
'\\\\server\\share\\' It's just missing the special case for "UNC" device paths that was recently implemented for >>> nt._path_normpath('//?/UNC/server/share/../..')
'\\\\?\\UNC\\' The result should keep the server and share components as part of the root. For example: >>> nt._path_splitroot('//?/UNC/server/share/../..')
('//?/UNC/server/share/', '../..') Additional notes on UNC handling The
|
This is no longer true as of 99fcf15, see python#96290
If >>> ntpath.splitdrive("///conky/mountpoint/foo/bar")
('///conky', '/mountpoint/foo/bar')
>>> ntpath.splitdrive("//conky//mountpoint/foo/bar")
('//conky/', '/mountpoint/foo/bar')
>>> ntpath.splitdrive("//?/UNC")
('//?/UNC', '')
>>> ntpath.splitdrive("//?/UNC/")
('//?/UNC/', '') For the most part, this change aligns with WinAPI Here's an example implementation: def splitdrive(p):
p = os.fspath(p)
if isinstance(p, bytes):
sep = b'\\'
altsep = b'/'
colon = b':'
unc_prefix = b'\\\\?\\UNC\\'
else:
sep = '\\'
altsep = '/'
colon = ':'
unc_prefix = '\\\\?\\UNC\\'
normp = p.replace(altsep, sep)
if normp[1:2] == colon and normp[:1] != sep:
# Drive-letter logical drives, e.g. X:
return p[:2], p[2:]
if normp[:2] == sep * 2:
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
# Device drives, e.g. \\.\device or \\?\device
start = 8 if normp[:8].upper() == unc_prefix else 2
index = normp.find(sep, start)
if index == -1:
return p, p[:0]
index = normp.find(sep, index + 1)
if index == -1:
return p, p[:0]
return p[:index], p[index:]
return p[:0], p |
Here's a small change to #ifdef MS_WINDOWS
// Skip past drive segment and update minP2
else if (p1[0] && !IS_SEP(&p1[0]) && p1[1] == L':') {
*p2++ = *p1++;
*p2++ = *p1++;
minP2 = p2;
lastC = L':';
}
// Skip past all \\ prefixed paths, including \\?\, \\.\,
// and network paths, including the first segment.
else if (IS_SEP(&p1[0]) && IS_SEP(&p1[1])) {
*p2++ = SEP;
*p2++ = SEP;
p1 += 2;
int maxSeps = 2;
int sepCount = 0;
for (; !IS_END(p1) && sepCount < maxSeps; ++p1) {
if (IS_SEP(p1)) {
sepCount++;
*p2++ = lastC = SEP;
if (sepCount == 2 && !_wcsnicmp(path, L"\\\\?\\UNC\\", 8)) {
maxSeps = 4;
}
} else {
*p2++ = lastC = *p1;
}
}
if (sepCount < maxSeps) {
minP2 = p2; // Implicit root or invalid path
} else {
minP2 = p2 - 1; // Absolute path has SEP at minP2
}
}
#else I switched to incrementing |
That's a lot of great analysis - I admit I haven't absorbed it all well enough to implement the changes, so hopefully Barney has. All I'll add is that I think it's critical to maintain the existing base cases when the split/dirname functions are applied recursively. So if path = get_path_for_this_demo()
while path:
do_something_with_path(path)
path = os.path.dirname(path) |
I agree with Eryk's earlier comment that we should handle everything beginning |
Supporting extended UNC paths in Additionally, the The implementation in def isabs(s):
"""Test whether a path is absolute"""
s = os.fspath(s)
if isinstance(s, bytes):
sep = b'\\'
altsep = b'/'
colon_sep = b':\\'
else:
sep = '\\'
altsep = '/'
colon_sep = ':\\'
s = s[:3].replace(altsep, sep)
# Absolute: UNC, device, and paths with a drive and root.
# LEGACY BUG: isabs("/x") should be false since the path has no drive.
if s.startswith(sep) or s.startswith(colon_sep, 1):
return True
return False |
This is no longer true as of 99fcf15, see python#96290
…splitdrive() This brings the Python implementation of `ntpath.normpath()` in line with the C implementation added in 99fcf15 Co-authored-by: Eryk Sun <eryksun@gmail.com>
…splitdrive() This brings the Python implementation of `ntpath.normpath()` in line with the C implementation added in 99fcf15 Co-authored-by: Eryk Sun <eryksun@gmail.com>
…splitdrive() This brings the Python implementation of `ntpath.normpath()` in line with the C implementation added in 99fcf15 Co-authored-by: Eryk Sun <eryksun@gmail.com>
PR available: #100351 |
…() and splitdrive() (pythonGH-100351) This brings the Python implementation of `ntpath.normpath()` in line with the C implementation added in 99fcf15 Co-authored-by: Eryk Sun <eryksun@gmail.com>
Fixed in 3.11 and 3.12. |
In Python 3.11.0rc1 on Windows:
In Python 3.11.0rc1 on non-Windows platforms, and in 3.10 across all platforms:
Linked PRs
The text was updated successfully, but these errors were encountered: