Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork34k
gh-74453: Deprecate os.path.commonprefix#144436
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Conversation
picnixz left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
You need a What's New entry for that.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Lib/string/__init__.py Outdated
| import os | ||
| m = tuple(map(os.fspath, m)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'm not really fond of this. string.commonprefix() should be specific to strings and should not care about pathlike objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yeah, this is only done for backwards compatibility. I would be happy to get rid of it, but I figured that this would be not allowed considering we're doing a "rename", not a new API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
There is no backwards compatibility here. We create a new function in a module that is not related to filepaths at all. I do not consider this as a rename, I really consider this as a new API. Instead, I would keep the implementation ofos.path.commonpath as is and add in a follow-up PRstring.commonprefix which does not do the fspath computation. We then document that os.path.commonpath should be reimplemented asstring.commonprefix(tuple(map(os.fspath, m))) if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I believe this newstring.commonprefix() should be a direct, one-to-one, drop-in replacement for the oldos.path.commonprefix().
We're not doing anyone favours by changing the behaviour and making them [have to decide whether they need to] jump through extra hoops.
Chances are they should be usingos.path.commonpath() instead, but if not, let them just replace the module name and keep the same old behaviour.
I doubt there's significant performance improvement from changing it either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
For me this isn't about performance, it's about semantics.string contains string-related utilities, not path-related ones. I am really opposed to havingstring.commonprefix being smart here. I know that we are not necessarily doing the user a favor but I don't want to have unrelated utilities instring. It is, for me, the wrong module for that.
Misc/NEWS.d/next/Library/2026-02-02-12-09-38.gh-issue-74453.19h4Z5.rst OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
hugovk left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Please also list inDoc/deprecations/pending-removal-in-future.rst.
https://docs.python.org/dev/deprecations/index.html#pending-removal-in-future-versions
Uh oh!
There was an error while loading.Please reload this page.
Lib/string/__init__.py Outdated
| import os | ||
| m = tuple(map(os.fspath, m)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I believe this newstring.commonprefix() should be a direct, one-to-one, drop-in replacement for the oldos.path.commonprefix().
We're not doing anyone favours by changing the behaviour and making them [have to decide whether they need to] jump through extra hoops.
Chances are they should be usingos.path.commonpath() instead, but if not, let them just replace the module name and keep the same old behaviour.
I doubt there's significant performance improvement from changing it either.
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
sethmlarson commentedFeb 3, 2026
Alright, most recent two commits should address all review comments. I have preserved backwards compatibility in the drop-in replacement |
Uh oh!
There was an error while loading.Please reload this page.
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
picnixz left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I am sorry, but I do not wantstring.commonprefix to be equivalent toos.path.commonprefix. Semantically it doesn't make sense to have an os-related utility instring.
In addition, we only talk about "strings" in the documentation. Having an implicitos.fspath conversion should be documented otherwise. If we want to mimicos.path.commonpath in the first place, I do not see why we can't just keep it but soft-deprecate it.
Note thatcommonprefix does not care about the fact that we are strings. It also works forany sequence, as long as the elements can be ordered (e.g., we can usecommonprefix for list of lists):
>>>os.path.commonpath([[1], [1,3]])[1]
Since we expose it instring, anyone using a non-string as the first argument willnot be able to make it work (because we raise a TypeError inos.fspath). So I would expect a string-like object to be acceptable but because ofos.fspath it wouldn't be. This would be contrary to what thestring module stands for.
If we want a commonprefix utility, I would suggest adding one toitertools, and use the latter. I believe it makes more sense to have something related tocommonprefix out there. Or for strings only, have it indifflib.
Uh oh!
There was an error while loading.Please reload this page.
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase And if you don't make the requested changes, you will be put in the comfy chair! |
sethmlarson commentedFeb 3, 2026
Because I want to prioritize warning users about this function, I am okay with not providing a |
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
d4c0bb4 to166c39dComparesethmlarson commentedFeb 3, 2026 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
| deftest_commonprefix(self): | ||
| commonprefix=self.pathmodule.commonprefix | ||
| self.assertEqual( | ||
| commonprefix([]), | ||
| "" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix(["/home/swenson/spam","/home/swen/spam"]), | ||
| "/home/swen" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix(["/home/swen/spam","/home/swen/eggs"]), | ||
| "/home/swen/" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix(["/home/swen/spam","/home/swen/spam"]), | ||
| "/home/swen/spam" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix(["home:swenson:spam","home:swen:spam"]), | ||
| "home:swen" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([":home:swen:spam",":home:swen:eggs"]), | ||
| ":home:swen:" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([":home:swen:spam",":home:swen:spam"]), | ||
| ":home:swen:spam" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([b"/home/swenson/spam",b"/home/swen/spam"]), | ||
| b"/home/swen" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([b"/home/swen/spam",b"/home/swen/eggs"]), | ||
| b"/home/swen/" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([b"/home/swen/spam",b"/home/swen/spam"]), | ||
| b"/home/swen/spam" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([b"home:swenson:spam",b"home:swen:spam"]), | ||
| b"home:swen" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([b":home:swen:spam",b":home:swen:eggs"]), | ||
| b":home:swen:" | ||
| ) | ||
| self.assertEqual( | ||
| commonprefix([b":home:swen:spam",b":home:swen:spam"]), | ||
| b":home:swen:spam" | ||
| ) | ||
| testlist= ['','abc','Xbcd','Xb','XY','abcd', | ||
| 'aXc','abd','ab','aX','abcX'] | ||
| fors1intestlist: | ||
| fors2intestlist: | ||
| p=commonprefix([s1,s2]) | ||
| self.assertStartsWith(s1,p) | ||
| self.assertStartsWith(s2,p) | ||
| ifs1!=s2: | ||
| n=len(p) | ||
| self.assertNotEqual(s1[n:n+1],s2[n:n+1]) | ||
| withwarnings_helper.check_warnings((".*commonpath().*",DeprecationWarning)): | ||
| commonprefix=self.pathmodule.commonprefix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
May I suggest that you have:
deftest_commonprefix(self):withwarnings_helper...:self.do_test_commonprefix()defdo_test_commonprefix(self):# the original test
That way, the diff will be smaller
| @@ -298,12 +299,13 @@ def test_isabs(self): | |||
| tester('ntpath.isabs("\\\\.\\C:")', 1) | |||
| def test_commonprefix(self): | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ditto
| m=sorted(m) | ||
| prefix=m[0] | ||
| foriteminm[1:]: | ||
| foriinrange(len(prefix)): | ||
| ifitem[i]!=prefix[i]: | ||
| prefix=prefix[:i] | ||
| break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Can we use the min/max + enumerate approach instead?
| s='%s[%d chars]%s'% (s[:prefixlen],skip,s[len(s)-suffixlen:]) | ||
| returns | ||
| def_commonprefix(m,/): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
| def_commonprefix(m,/): | |
| def_commonprefix(m): |
as it's private, no need to enforce the /
Uh oh!
There was an error while loading.Please reload this page.
Follow-up from#144401, this PR moves to deprecate
os.path.commonprefix()in favor of the correctly behavingos.path.commonpath()and the behavior-describingstring.commonprefix().I'm not sure if this is all I have to do to deprecate an API, if there is more documentation that needs to happen let me know.
📚 Documentation preview 📚:https://cpython-previews--144436.org.readthedocs.build/