Python: получить относительный путь из сравнения двух абсолютных путей

Скажем, у меня есть два абсолютных пути. Мне нужно проверить, является ли местоположение, на которое ссылается один из путей, потомком другого. Если это правда, мне нужно выяснить относительный путь потомка от предка. Какой хороший способ реализовать это в Python? Любая библиотека, из которой я могу извлечь выгоду?

115 голосов | спросил tamakisquare 2 ndEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 02 Sep 2011 22:52:42 +0400 2011, 22:52:42

5 ответов


0

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]

С помощью этого метода вы даже можете обрабатывать более двух путей и проверить, все ли пути ниже одного из них.

PS : в зависимости от того, как выглядят ваши пути, вы можете сначала выполнить некоторую нормализацию (это полезно в ситуациях, когда не известно, всегда ли они заканчиваются на '/' или нет, или если некоторые из путей являются относительными). Соответствующие функции включают в себя os.path.abspath () и os.path.normpath () .

PPS : как упоминал Питер Бриггс в комментариях, простой подход, описанный выше, может потерпеть неудачу:

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

, хотя /usr/var не является , а обычным префиксом путей. Принудительное завершение всех путей знаком '/' перед вызовом commonprefix() решает эту (специфическую) проблему.

PPPS : как упоминалось в bluenote10, добавление косой черты не решает общую проблему. Вот его следующий вопрос: Как обойти ошибку Os.path.commonprefix в Python?

PPPPS : начиная с Python 3.4 у нас есть pathlib , модуль, обеспечивающий более разумную среду манипулирования путями. Я предполагаю, что общий префикс набора путей можно получить, получив все префиксы каждого пути (с помощью PurePath.parents() ), принимая пересечение всех этих родительских наборов и выбирая самый длинный общий префикс.

PPPPPS : Python 3.5 представил правильное решение этого вопроса: os.path.commonpath() , который возвращает правильный путь.

ответил Eric O Lebigot 2 ndEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 02 Sep 2011 22:54:21 +0400 2011, 22:54:21
0

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'
ответил warvariuc 2 ndEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 02 Sep 2011 23:00:00 +0400 2011, 23:00:00
0

Другой вариант -

>>> print os.path.relpath('/usr/var/log/', '/usr/var')
log
ответил 2 ndEurope/Moscowp30Europe/Moscow09bEurope/MoscowFri, 02 Sep 2011 23:00:53 +0400 2011, 23:00:53
0

Я искал решение с python2 и без каких-либо внешних зависимостей. Не нашел ничего соответствующего моим потребностям. И хотя commonprefix сравнивает только строки, а не элементы пути, я написал следующее:

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:])
    if newpath:
        return os.path.join(*newpath)
    return "."

Любые комментарии приветствуются!

ответил Jan Stürtz 15 Maypm17 2017, 17:32:12
0

Правка . Чтобы узнать, как лучше всего использовать 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.

ответил Jeremy Cochoy 26 SatEurope/Moscow2015-12-26T06:50:56+03:00Europe/Moscow12bEurope/MoscowSat, 26 Dec 2015 06:50:56 +0300 2015, 06:50:56

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132