lenguaje - python tutorial
Python: obtenga una ruta relativa desde la comparaciĆ³n de dos rutas absolutas (5)
Diga, tengo dos caminos absolutos. Necesito comprobar si la ubicación a la que hace referencia una de las rutas es descendiente de la otra. Si es cierto, necesito averiguar el camino relativo del descendiente del antepasado. ¿Cuál es una buena manera de implementar esto en Python? ¿Cualquier biblioteca de la que pueda beneficiarme?
Busqué una solución con python2 y sin ninguna dependencia externa. No encontré nada que coincida con mis necesidades. Y aunque Commonprefix, solo compara cadenas y no path elems, escribí esto:
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)
Cualquier comentario bienvenido!
Otra opción es
>>> print os.path.relpath(''/usr/var/log/'', ''/usr/var'')
log
os.path.commonprefix() y os.path.relpath() son tus amigos:
>>> 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
''/''
Por lo tanto, puede probar si el prefijo común es una de las rutas, es decir, si una de las rutas es un antecesor común:
paths = […, …, …]
common_prefix = os.path.commonprefix(list_of_paths)
if common_prefix in paths:
…
A continuación, puede encontrar las rutas relativas:
relative_paths = [os.path.relpath(path, common_prefix) for path in paths]
Incluso puede manejar más de dos rutas con este método y probar si todas las rutas están todas debajo de una de ellas.
PD : dependiendo de cómo se vean sus rutas, es posible que desee realizar primero una cierta normalización (esto es útil en situaciones donde no se sabe si siempre terminan con ''/'' o no, o si algunas de las rutas son relativas). Las funciones relevantes incluyen os.path.abspath() y os.path.normpath() .
PPS : como Peter Briggs mencionó en los comentarios, el enfoque simple descrito anteriormente puede fallar:
>>> os.path.commonprefix([''/usr/var'', ''/usr/var2/log''])
''/usr/var''
aunque /usr/var
no es un prefijo común de las rutas. Obligar a todas las rutas a terminar con ''/'' antes de llamar a commonprefix()
resuelve este problema (específico).
PPPS : como se menciona en bluenote10, agregar una barra no resuelve el problema general. Aquí está su pregunta de seguimiento: ¿Cómo eludir la falacia de os.path.commonprefix de Python?
PPPPS : comenzando con Python 3.4, tenemos pathlib , un módulo que proporciona un entorno de manipulación de ruta más pathlib . Supongo que se puede obtener el prefijo común de un conjunto de rutas obteniendo todos los prefijos de cada ruta (con PurePath.parents()
), tomando la intersección de todos estos conjuntos principales y seleccionando el prefijo común más largo.
PPPPPS : Python 3.5 introdujo una solución adecuada a esta pregunta: os.path.commonpath()
, que devuelve una ruta válida.
Devuelve una ruta de archivo relativa a la ruta, ya sea desde el directorio actual o desde un punto de inicio opcional.
>>> from os.path import relpath
>>> relpath(''/usr/var/log/'', ''/usr/var'')
''log''
>>> relpath(''/usr/var/log/'', ''/usr/var/sad/'')
''../log''
Entonces, si la ruta relativa comienza con ''..''
- significa que la segunda ruta no es descendiente de la primera ruta.
En Python3 puedes usar 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''
Editar: vea la respuesta de jme de la mejor manera con Python3.
Usando pathlib, tienes la siguiente solución:
Digamos que queremos verificar si son
es descendiente de uno de los parent
, y ambos son objetos de Path
. Podemos obtener una lista de las partes en la ruta con list(parent.parts)
. Luego, solo comprobamos que el comienzo del hijo es igual a la lista de segmentos del padre.
>>> lparent = list(parent.parts)
>>> lson = list(son.parts)
>>> if lson[:len(lparent)] == lparent:
>>> ... #parent is a parent of son :)
Si quieres obtener la parte restante, puedes hacer
>>> ''''.join(lson[len(lparent):])
Es una cadena, pero por supuesto puedes usarla como constructor de otro objeto Path.