回答:
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
'/'
したがって、共通のプレフィックスがパスの1つであるかどうか、つまり、パスの1つが共通の祖先であるかどうかをテストできます。
paths = […, …, …]
common_prefix = os.path.commonprefix(list_of_paths)
if common_prefix in paths:
…
次に、相対パスを見つけることができます。
relative_paths = [os.path.relpath(path, common_prefix) for path in paths]
この方法では、3つ以上のパスを処理し、すべてのパスがいずれか1つを下回っているかどうかをテストすることもできます。
PS:パスがどのように見えるかに応じて、最初にいくつかの正規化を実行する必要があります(これは、パスが常に「/」で終わるかどうかがわからない場合や、一部のパスが相対パスである場合に役立ちます)。関連する関数には、os.path.abspath()およびos.path.normpath()があります。ます。
PPS:Peter Briggsがコメントで述べたように、上記の単純なアプローチは失敗する可能性があります:
>>> os.path.commonprefix(['/usr/var', '/usr/var2/log'])
'/usr/var'
たとえ/usr/varではないパスの共通のプレフィックス。呼び出す前にすべてのパスを強制的に「/」で終了するcommonprefix()すると、この(特定の)問題が解決します。
PPPS:bluenote10で述べたように、スラッシュを追加しても一般的な問題は解決されません。これが彼のフォローアップの質問です:Pythonのos.path.commonprefixの誤りをどうやって回避するのですか?
PPPPS:Pythonの3.4で始まる、私たちは持っているpathlib、正気パス操作環境を提供するモジュールを。パスのセットの共通の接頭辞は、各パスのすべての接頭辞を(でPurePath.parents())取得し、これらすべての親セットの共通部分を取り、最も長い共通の接頭辞を選択することで取得できると思います。
PPPPPS:Python 3.5ではos.path.commonpath()、有効なパスを返すこの質問に対する適切なソリューションが導入されました。
commonprefixための共通の接頭辞を、たとえばとして、/usr/var/log及び/usr/var2/logとして返されます/usr/var-あなたが期待するものはおそらくされていません。(また、無効なディレクトリであるパスを返すことも可能です。)
['/usr/var1/log/', '/usr/var2/log/']?
現在のディレクトリまたはオプションの開始点からpathへの相対ファイルパスを返します。
>>> from os.path import relpath
>>> relpath('/usr/var/log/', '/usr/var')
'log'
>>> relpath('/usr/var/log/', '/usr/var/sad/')
'../log'
したがって、相対パスが次で始まる場合-2 '..'番目のパスが最初のパスの子孫ではないことを意味します。
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'
os.pardirためにチェックするよりも堅牢である..(合意された、他の多くの規則はしかし、そこにはありません)。
os.relpath処理..して処理PurePath.relative_to()しないのでより強力ですか?何か不足していますか?
別のオプションは
>>> print os.path.relpath('/usr/var/log/', '/usr/var')
log
os.pardirただし、2つの可能な相対パスの前に存在を確認できます)。
Python 3でのpathlibを使用したjmeの提案のまとめ。
from pathlib import Path
parent = Path(r'/a/b')
son = Path(r'/a/b/c/d')
if parent in son.parents or parent==son:
print(son.relative_to(parent)) # returns Path object equivalent to 'c/d'
dir1.relative_to(dir2)PosixPathを与えるだろう(「」)それらが同じである場合。使用するif dir2 in dir1.parentsと、IDケースが除外されます。誰かがパスを比較しrelative_to()ていて、パスに互換性がある場合に実行したい場合、より良い解決策はif dir2 in (dir1 / 'x').parentsまたはif dir2 in dir1.parents or dir2 == dir1です。次に、パスの互換性のすべてのケースがカバーされます。
純粋なPython2、depなし:
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)
eq_until_pos = None
for i in xrange(min(len(_cwd), len(_path))):
if _cwd[i] == _path[i]:
eq_until_pos = i
else:
break
if eq_until_pos is None:
return path
newpath = [".." for i in xrange(len(_cwd[eq_until_pos+1:]))]
newpath.extend(_path[eq_until_pos+1:])
return os.path.join(*newpath) if newpath else "."
cwdとpathが同じ場合に問題があります。これらの2つが同じとリターンのどちらかであるならば、それは最初にチェックすべき""か"."
編集: Python3での最良の方法については、jmeの回答を参照してください。
pathlibを使用すると、次の解決策があります。
sonがの子孫でparentあり、両方が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):])
これは文字列ですが、もちろん他のPathオブジェクトのコンストラクターとして使用できます。
parent in son.parentsに、もしそうなら、で残りを取得しson.relative_to(parent)ます。