[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
'/'

따라서 공통 접두사가 경로 중 하나인지, 즉 경로 중 하나가 공통 조상인지 여부를 테스트 할 수 있습니다.

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]

이 방법을 사용하여 둘 이상의 경로를 처리하고 모든 경로가 모두 하나 아래에 있는지 테스트 할 수 있습니다.

추신 : 경로의 모양에 따라 먼저 정규화를 수행하려고 할 수 있습니다 (이것은 항상 ‘/’로 끝나는 지 아닌지 또는 일부 경로가 상대적인지 모르는 상황에서 유용합니다). 관련 함수에는 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(). 유효한 경로를 반환합니다.


답변

os.path.relpath:

현재 디렉토리 또는 선택적 시작점에서 상대 경로를 경로로 리턴하십시오.

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

따라서 상대 경로로 시작 '..'하면 두 번째 경로가 첫 번째 경로의 후손이 아님을 의미합니다.

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'


답변

다른 옵션은

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


답변

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'


답변

순수 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 "."


답변

편집 : 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 객체의 생성자로 사용할 수도 있습니다.


답변