Python:从比较两个绝对path获取相对path

说,我有两条绝对path。 我需要检查一个path所指的位置是否是另一个的后裔。 如果属实,我需要从祖先中找出后代的相对path。 什么是在Python中实现这个好方法? 任何我可以从中受益的图书馆?

os.path.commonprefix()和os.path.relpath()是你的朋友:

>>> print os.path.commonprefix(['/usr/var/log', '/usr/var/security']) '/usr/var' >>> print os.path.commonprefix(['/tmp', '/usr/var']) # No common prefix: the root is the common prefix '/' 

因此,您可以testing通用前缀是否是path之一,也就是说,如果其中一个path是共同的祖先:

 paths = […, …, …] common_prefix = os.path.commonprefix(list_of_paths) if common_prefix in paths: … 

您可以find相对path:

 relative_paths = [os.path.relpath(path, common_prefix) for path in paths] 

你甚至可以用这个方法处理两个以上的path,并且testing所有的path是否都在它们之下。

PS :取决于你的path是什么样的,你可能首先要执行一些规范化(这在一个不知道它们总是以'/'结尾还是一些path是相对的情况下很有用)。 相关函数包括os.path.abspath()和os.path.normpath() 。

PPS :正如Peter Briggs在评论中提到的那样,上述简单的方法可能会失败:

 >>> os.path.commonprefix(['/usr/var', '/usr/var2/log']) '/usr/var' 

即使/usr/var 不是path的通用前缀。 在调用commonprefix()之前强制所有path以“/”结尾,以解决这个(特定的)问题。

PPPS :正如bluenote10所提到的那样,加一个斜杠并不能解决一般问题。 这里是他的后续问题: 如何绕过Python的os.path.commonprefix的谬误?

PPPPS :从Python 3.4开始,我们有了pathlib ,这个模块提供了一个更好的path操作环境。 我想通过获取每个path的所有前缀(使用PurePath.parents() ),取所有这些父集的交集,并select最长的公共前缀,可以获得一组path的通用前缀。

PPPPPS :Python 3.5为这个问题引入了一个合适的解决scheme: os.path.commonpath() ,它返回一个有效的path。

os.path.relpath

将相对path从当前目录或从可选起点返回到path。

 >>> from os.path import relpath >>> relpath('/usr/var/log/', '/usr/var') 'log' >>> relpath('/usr/var/log/', '/usr/var/sad/') '../log' 

所以,如果相对path以'..'开始 – 这意味着第二个path不是第一个path的后代。

在Python3中,您可以使用PurePath.relative_to

 Python 3.5.1 (default, Jan 22 2016, 08:54:32) >>> from pathlib import Path >>> Path('/usr/var/log').relative_to('/usr/var/log/') PosixPath('.') >>> Path('/usr/var/log').relative_to('/usr/var/') PosixPath('log') >>> Path('/usr/var/log').relative_to('/etc/') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/pathlib.py", line 851, in relative_to .format(str(self), str(formatted))) ValueError: '/usr/var/log' does not start with '/etc' 

另一个select是

 >>> print os.path.relpath('/usr/var/log/', '/usr/var') log 

我寻找与python2和w / o任何外部依赖的解决scheme。 没有find符合我需求的任何内容。 虽然commonprefix,只比较string,而不是pathelems,我写这个:

 def _relpath(cwd, path): # Create a relative path for path from cwd, if possible if sys.platform == "win32": cwd = cwd.lower() path = path.lower() _cwd = os.path.abspath(cwd).split(os.path.sep) _path = os.path.abspath(path).split(os.path.sep) equal_until_pos = None for i in xrange(min(len(_cwd), len(_path))): if _cwd[i] != _path[i]: break else: equal_until_pos = i if equal_until_pos is None: return path newpath = [".." for i in xrange(len(_cwd[equal_until_pos + 1:]))] newpath.extend(_path[equal_until_pos + 1:]) return os.path.join(*newpath) 

欢迎任何评论!

编辑:请参阅jme的答案与Python3的最佳方式。

使用pathlib,你有以下解决scheme:

假设我们想检查一下子是否是parent的后代,并且都是Path对象。 我们可以通过list(parent.parts)获取path中的部分 list(parent.parts) 。 然后,我们只是检查儿子的开头是否等于父母的段的列表。

 >>> lparent = list(parent.parts) >>> lson = list(son.parts) >>> if lson[:len(lparent)] == lparent: >>> ... #parent is a parent of son :) 

如果你想得到剩余的部分,你可以做

 >>> ''.join(lson[len(lparent):]) 

这是一个string,但是当然可以用它作为其他Path对象的构造函数。