example - ¿Cómo evitar la falacia de os.path.commonprefix de Python?
python current path (5)
Hace un tiempo me encontré con esto donde os.path.commonprefix
es un prefijo de cadena y no un prefijo de ruta como se esperaría. Así que escribí lo siguiente:
def commonprefix(l):
# this unlike the os.path.commonprefix version
# always returns path prefixes as it compares
# path component wise
cp = []
ls = [p.split(''/'') for p in l]
ml = min( len(p) for p in ls )
for i in range(ml):
s = set( p[i] for p in ls )
if len(s) != 1:
break
cp.append(s.pop())
return ''/''.join(cp)
podría hacerse más portátil reemplazando ''/''
con os.path.sep
.
Mi problema es encontrar el prefijo de ruta común de un conjunto dado de archivos.
Literalmente esperaba que "os.path.commonprefix" hiciera exactamente eso. Desafortunadamente, el hecho de que el commonprefix
esté ubicado en la path
es bastante engañoso, ya que en realidad buscará los prefijos de cadena.
La pregunta para mí es, ¿cómo se puede resolver esto realmente por caminos? El problema se mencionó brevemente en esta respuesta (de calificación bastante alta) pero solo como una nota al margen y la solución propuesta (agregando barras a la entrada de prefijo común) Imho tiene problemas, ya que fallará por ejemplo para:
os.path.commonprefix([''/usr/var1/log/'', ''/usr/var2/log/''])
# returns /usr/var but it should be /usr
Para evitar que otros caigan en la misma trampa, podría valer la pena discutir este problema en otra pregunta: ¿Existe una solución simple / portátil para este problema que no se base en verificaciones desagradables del sistema de archivos (es decir, acceda al resultado)? de commonprefix y compruebe si es un directorio y si no devuelve un os.path.dirname
del resultado)?
Hice una pequeña commonpath
común de commonpath
para encontrar rutas comunes de una lista. Viene con algunas opciones agradables.
Parece que este problema se ha corregido en las versiones recientes de Python. Nueva en la versión 3.5 es la función os.path.commonpath()
, que devuelve la ruta común en lugar del prefijo de cadena común.
Suponiendo que desea la ruta de directorio común, una forma es:
- Utilice solo las rutas de directorio como entrada. Si su valor de entrada es un nombre de archivo, llame a
os.path.dirname(filename)
para obtener su ruta de directorio. - "Normalice" todos los caminos para que sean relativos a la misma cosa y no incluyan separadores dobles. La forma más sencilla de hacerlo es llamando a
os.path.abspath( )
para obtener la ruta relativa a la raíz. (Es posible que también desee utilizaros.path.realpath( )
para eliminar enlaces simbólicos). - Agregue un separador final (que se encuentra de forma portátil con
os.path.sep
oos.sep
) al final de todas las rutas de directorio normalizadas. - Llame a
os.path.dirname( )
en el resultado deos.path.commonprefix( )
.
En código (sin eliminar enlaces simbólicos):
def common_path(directories):
norm_paths = [os.path.abspath(p) + os.path.sep for p in directories]
return os.path.dirname(os.path.commonprefix(norm_paths))
def common_path_of_filenames(filenames):
return common_path([os.path.dirname(f) for f in filenames])
Un enfoque sólido es dividir la ruta en componentes individuales y luego encontrar el prefijo común más largo de las listas de componentes.
Aquí hay una implementación que es multiplataforma y se puede generalizar fácilmente a más de dos caminos:
import os.path
import itertools
def components(path):
''''''
Returns the individual components of the given file path
string (for the local operating system).
The returned components, when joined with os.path.join(), point to
the same location as the original path.
''''''
components = []
# The loop guarantees that the returned components can be
# os.path.joined with the path separator and point to the same
# location:
while True:
(new_path, tail) = os.path.split(path) # Works on any platform
components.append(tail)
if new_path == path: # Root (including drive, on Windows) reached
break
path = new_path
components.append(new_path)
components.reverse() # First component first
return components
def longest_prefix(iter0, iter1):
''''''
Returns the longest common prefix of the given two iterables.
''''''
longest_prefix = []
for (elmt0, elmt1) in itertools.izip(iter0, iter1):
if elmt0 != elmt1:
break
longest_prefix.append(elmt0)
return longest_prefix
def common_prefix_path(path0, path1):
return os.path.join(*longest_prefix(components(path0), components(path1)))
# For Unix:
assert common_prefix_path(''/'', ''/usr'') == ''/''
assert common_prefix_path(''/usr/var1/log/'', ''/usr/var2/log/'') == ''/usr''
assert common_prefix_path(''/usr/var/log1/'', ''/usr/var/log2/'') == ''/usr/var''
assert common_prefix_path(''/usr/var/log'', ''/usr/var/log2'') == ''/usr/var''
assert common_prefix_path(''/usr/var/log'', ''/usr/var/log'') == ''/usr/var/log''
# Only for Windows:
# assert common_prefix_path(r''C:/Programs/Me'', r''C:/Programs'') == r''C:/Programs''