16.2. Encontrar la ruta

A veces es útil, cuando ejecutamos scripts de Python desde la línea de órdenes, saber dónde está situado en el disco el script que está en marcha.

Éste es uno de esos pequeños trucos que es virtualmente imposible que averigüe por su cuenta, pero que es simple de recordar una vez lo ha visto. La clave es sys.argv. Como vio en Capítulo 9, Procesamiento de XML, almacena una lista de los argumentos de la línea de órdenes. Sin embargo, también contiene el nombre del script que está funcionando, exactamente igual que se le invocó en la línea de órdenes, y es información suficiente para determinar su situación.

Ejemplo 16.3. fullpath.py

Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.


import sys, os

print 'sys.argv[0] =', sys.argv[0]             1
pathname = os.path.dirname(sys.argv[0])        2
print 'path =', pathname
print 'full path =', os.path.abspath(pathname) 3
1 Independientemente de la manera en que ejecute un script, sys.argv[0] contendrá siempre el nombre del script exactamente tal como aparece en la línea de órdenes. Esto puede que incluya información de la ruta o no, como veremos en breve.
2 os.path.dirname toma una cadena con un nombre de fichero y devuelve la porción correspondiente a la ruta. Si el nombre de fichero no incluye información de ruta, os.path.dirname devuelve una cadena vacía.
3 Aquí lo importante es os.path.abspath. Toma el nombre de una ruta, que puede ser parcial o incluso estar vacía, y devuelve un nombre de ruta totalmente calificado.

os.path.abspath merece una mejor explicación. Es muy flexible; puede tomar cualquier tipo de ruta.

Ejemplo 16.4. Más explicaciones sobre os.path.abspath

>>> import os
>>> os.getcwd()                        1
/home/usted
>>> os.path.abspath('')                2
/home/usted
>>> os.path.abspath('.ssh')            3
/home/usted/.ssh
>>> os.path.abspath('/home/usted/.ssh') 4
/home/usted/.ssh
>>> os.path.abspath('.ssh/../foo/')    5
/home/usted/foo
1 os.getcwd() devuelve el directorio de trabajo actual.
2 Invocar a os.path.abspath con una cadena vacía devuelve el directorio actual de trabajo, igual que os.getcwd().
3 Una invocación a os.path.abspath pasando una ruta parcial construye un nombre de ruta completamente calificada, basándose en el directorio actual de trabajo.
4 Llamar a os.path.abspath con una ruta completa la devuelve sin modificaciones.
5 os.path.abspath también normaliza el nombre de ruta que devuelve. Observe que este ejemplo ha funcionado incluso aunque no tengo un directorio "foo". os.path.abspath nunca comprueba el disco realmente; es todo cuestión de manipulación de cadenas.
nota
No hace falta que existan los nombres de rutas y ficheros que se le pasan a os.path.abspath.
nota
os.path.abspath no se limita a construir nombres completos de rutas, también los normaliza. Eso significa que si está en el directorio /usr/, os.path.abspath('bin/../local/bin') devolverá /usr/local/bin. Normaliza la ruta haciéndola lo más sencilla posible. Si sólo quiere normalizar una ruta así, sin convertirla en completa, use os.path.normpath.

Ejemplo 16.5. Salida de ejemplo de fullpath.py

[usted@localhost py]$ python /home/usted/diveintopython/common/py/fullpath.py 1
sys.argv[0] = /home/usted/diveintopython/common/py/fullpath.py
path = /home/usted/diveintopython/common/py
full path = /home/usted/diveintopython/common/py
[usted@localhost diveintopython]$ python common/py/fullpath.py               2
sys.argv[0] = common/py/fullpath.py
path = common/py
full path = /home/usted/diveintopython/common/py
[usted@localhost diveintopython]$ cd common/py
[usted@localhost py]$ python fullpath.py                                     3
sys.argv[0] = fullpath.py
path = 
full path = /home/usted/diveintopython/common/py
1 En el primer caso, sys.argv[0] incluye la ruta completa del script. Puede usar la función os.path.dirname para eliminar el nombre del script y devolver el nombre completo del directorio, y os.path.abspath nos devuelve simplemente lo mismo que le pasamos.
2 Si se ejecuta el script usando una ruta parcial, sys.argv[0] seguirá conteniendo exactamente lo que aparece en la línea de órdenes. os.path.dirname nos devolverá una ruta parcial (relativa al directorio actual) y os.path.abspath construirá una ruta completa partiendo de la parcial.
3 Si se ejecuta el script desde el directorio actual sin dar ninguna ruta, os.path.dirname devolverá una cadena vacía. Dada una cadena vacía, os.path.abspath devuelve el directorio actual, que es lo que queríamos ya que el script se ejecutó desde el directorio actual.
nota
os.path.abspath es multiplataforma igual que las otras funciones del os módulos os y os.path. Los resultados parecerán ligeramente diferentes que en mis ejemplos si está usted usando Windows (que usa barras invertidas como separador de ruta) o Mac OS (que usa dos puntos - :), pero seguirá funcionando. Ésa es la idea principal del módulo os.

Adenda. Un lector no estaba satisfecho con esta solución y quería poder ejecutar todas las pruebas unitarias en el directorio actual, no en aquél donde se encontrase regression.py. Sugirió este enfoque alternativo:

Ejemplo 16.6. Ejecución de scripts en el directorio actual

import sys, os, re, unittest

def regressionTest():
    path = os.getcwd()       1
    sys.path.append(path)    2
    files = os.listdir(path) 3
1 En lugar de poner en path el directorio donde está situado el script que está ejecutándose, ponemos el directorio actual de trabajo. Éste será el directorio donde estuviésemos en el momento de ejecutarlo, y no tiene por qué coincidir necesariamente con el del script (lea esta frase un par de veces hasta que la entienda bien).
2 Añada este directorio a la ruta de búsqueda de bibliotecas de Python, para que Python pueda encontrar los módulos de pruebas unitarias cuando los vaya a importar dinámicamente. No necesitamos hacer esto cuando path coincide con el directorio del script en ejecución, porque Python siempre mira en ese directorio.
3 El resto de la función es igual.

Esta técnica le permitirá reutilizar este script regression.py en varios proyectos. Simplemente lo pone en un directorio común, y cambia al directorio del proyecto antes de ejecutarlo. Encontrará y ejecutará todas las pruebas unitarias de ese proyecto en lugar de las que se encuentren en el mismo directorio de regression.py.