6.5. Trabajo con directorios

El módulo os.path tiene varias funciones para manipular ficheros y directorios. Aquí queremos manipular rutas y listar el contenido de un directorio.

Ejemplo 6.16. Construcción de rutas

>>> import os
>>> os.path.join("c:\\music\\ap\\", "mahadeva.mp3") 1 2
'c:\\music\\ap\\mahadeva.mp3'
>>> os.path.join("c:\\music\\ap", "mahadeva.mp3")   3
'c:\\music\\ap\\mahadeva.mp3'
>>> os.path.expanduser("~")                         4
'c:\\Documents and Settings\\mpilgrim\\My Documents'
>>> os.path.join(os.path.expanduser("~"), "Python") 5
'c:\\Documents and Settings\\mpilgrim\\My Documents\\Python'
1 os.path es una referencia a un módulo (qué módulo exactamente, depende de su plataforma). Al igual que getpass encapsula las diferencias entre plataformas asociando getpass a una función específica, os encapsula las diferencias entre plataformas asociando path a un módulo específico para la suya.
2 La función join de os.path construye el nombre de una ruta partiendo de una o más rutas parciales. En este caso, simplemente concatena cadenas (observe que trabajar con nombres de rutas en Windows es molesto debido a que hay que escapar la barra inversa).
3 En este caso ligeramente menos trivial, join añadirá una barra inversa extra a la ruta antes de unirla al nombre de fichero. Quedé encantadísimo cuando descubrí esto, ya que addSlashIfNecessary es una de las pequeñas funciones estúpidas que siempre tengo que escribir cuando escribo mis propias herramientas en un nuevo lenguaje. No escriba esta pequeña estúpida función en Python: hay gente inteligente que ya lo ha hecho por usted.
4 expanduser expandirá un nombre de ruta que utilice ~ para representar el directorio personal del usuario actual. Esto funciona en cualquier plataforma donde los usuarios tengan directorios personales, como Windows, UNIX y Mac OS X; no tiene efecto en Mac OS.
5 Combinando estas técnicas, puede construir fácilmente rutas para directorios y ficheros bajo el directorio personal del usuario.

Ejemplo 6.17. Dividir nombres de rutas

>>> os.path.split("c:\\music\\ap\\mahadeva.mp3")                        1
('c:\\music\\ap', 'mahadeva.mp3')
>>> (filepath, filename) = os.path.split("c:\\music\\ap\\mahadeva.mp3") 2
>>> filepath                                                            3
'c:\\music\\ap'
>>> filename                                                            4
'mahadeva.mp3'
>>> (shortname, extension) = os.path.splitext(filename)                 5
>>> shortname
'mahadeva'
>>> extension
'.mp3'
1 La función split divide una ruta completa y devuelve una tupla que contiene la ruta y el nombre del fichero. ¿Recuerda cuando dije que podría usar la asignación de múltiples variables para recoger varios valores de una función? Bien, split es una de esas funciones.
2 Asignamos el valor de retorno de la función split a una tupla con dos variables. Cada variable recibe el valor del elemento correspondiente de la tupla devuelta.
3 La primera variable, filepath, recibe el valor del primer elemento de la tupla devuelta por split, la ruta hasta el fichero.
4 La segunda variable, filename, recibe el valor del segundo elemento de la tupla devuelta por split, el nombre del fichero.
5 os.path también contiene una función splitext, que divide un nombre de fichero y devuelve una tupla que contiene el nombre y la extensión. Usamos la misma técnica para asignar cada una de ellas a variables distintas.

Ejemplo 6.18. Listado de directorios

>>> os.listdir("c:\\music\\_singles\\")              1
['a_time_long_forgotten_con.mp3', 'hellraiser.mp3',
'kairo.mp3', 'long_way_home1.mp3', 'sidewinder.mp3', 
'spinning.mp3']
>>> dirname = "c:\\"
>>> os.listdir(dirname)                              2
['AUTOEXEC.BAT', 'boot.ini', 'CONFIG.SYS', 'cygwin',
'docbook', 'Documents and Settings', 'Incoming', 'Inetpub', 'IO.SYS',
'MSDOS.SYS', 'Music', 'NTDETECT.COM', 'ntldr', 'pagefile.sys',
'Program Files', 'Python20', 'RECYCLER',
'System Volume Information', 'TEMP', 'WINNT']
>>> [f for f in os.listdir(dirname)
...     if os.path.isfile(os.path.join(dirname, f))] 3
['AUTOEXEC.BAT', 'boot.ini', 'CONFIG.SYS', 'IO.SYS', 'MSDOS.SYS',
'NTDETECT.COM', 'ntldr', 'pagefile.sys']
>>> [f for f in os.listdir(dirname)
...     if os.path.isdir(os.path.join(dirname, f))]  4
['cygwin', 'docbook', 'Documents and Settings', 'Incoming',
'Inetpub', 'Music', 'Program Files', 'Python20', 'RECYCLER',
'System Volume Information', 'TEMP', 'WINNT']
1 La función listdir toma una ruta y devuelve una lista con el contenido de ese directorio.
2 listdir devuelve tanto ficheros como carpetas, sin indicar qué cosa es cada uno.
3 Puede usar el filtrado de listas y la función isfile del módulo os.path para separar los ficheros de los directorios. isfile toma una ruta y devuelve 1 si representa un fichero, y un 0 si no. Aquí estamos usando os.path.join para asegurarnos de tener una ruta completa, pero isfile también funciona con rutas parciales, relativas al directorio actual de trabajo. Puede usar os.getcwd() para obtener el directorio de trabajo.
4 os.path también tiene una función isdir que devuelve 1 si la ruta representa un directorio y 0 si no. Puede usarlo para obtener una lista de los subdirectorios dentro de un directorio.

Ejemplo 6.19. Listado de directorios en fileinfo.py


def listDirectory(directory, fileExtList):                                        
    """obtener lista de objetos de información sobre ficheros de
extensiones particulares""" 
    fileList = [os.path.normcase(f)
                for f in os.listdir(directory)]            1 2
    fileList = [os.path.join(directory, f) 
               for f in fileList
                if os.path.splitext(f)[1] in fileExtList]  3 4 5
1 os.listdir(directory) devuelve una lista de todos los ficheros y directorios en directory.
2 Iterando sobre la lista con f, usamos os.path.normcase(f) para normalizar las mayúsculas y minúsculas de acuerdo a los valores por omisión del sistema operativo. normcase es una función pequeña y útil que compensa en los sistemas operativos que no distinguen las mayúsculas y por tanto piensan que mahadeva.mp3 y mahadeva.MP3 son el mismo fichero. Por ejemplo, en Windows y Mac OS normcase convertirá todo el nombre del fichero a minúsculas; en los sistemas compatibles con UNIX devolverá el nombre sin cambios.
3 Iterando sobre la lista normalizada de nuevo con f, usamos os.path.splitext(f) para dividir cada nombre de fichero en nombre y extensión.
4 Por cada fichero, vemos si la extensión está en la lista de extensiones de ficheros que nos ocupan (fileExtList, que se pasó a la función listDirectory).
5 Por cada fichero que nos interesa, usamos os.path.join(directory, f) para construir la ruta completa hasta el fichero, y devolvemos una lista de las rutas absolutas.
nota
Siempre que sea posible, debería usar las funciones de os y os.path para manipulaciones sobre ficheros, directorios y rutas. Estos módulos encapsulan lo específico de cada plataforma, de manera que funciones como os.path.split funcionen en UNIX, Windows, Mac OS y cualquier otra plataforma en que funcione Python.

Hay otra manera de obtener el contenido de un directorio. Es muy potente, y utiliza los comodines con los que posiblemente esté familiarizado al trabajar en la línea de órdenes.

Ejemplo 6.20. Listado de directorios con glob

>>> os.listdir("c:\\music\\_singles\\")               1
['a_time_long_forgotten_con.mp3', 'hellraiser.mp3',
'kairo.mp3', 'long_way_home1.mp3', 'sidewinder.mp3',
'spinning.mp3']
>>> import glob
>>> glob.glob('c:\\music\\_singles\\*.mp3')           2
['c:\\music\\_singles\\a_time_long_forgotten_con.mp3',
'c:\\music\\_singles\\hellraiser.mp3',
'c:\\music\\_singles\\kairo.mp3',
'c:\\music\\_singles\\long_way_home1.mp3',
'c:\\music\\_singles\\sidewinder.mp3',
'c:\\music\\_singles\\spinning.mp3']
>>> glob.glob('c:\\music\\_singles\\s*.mp3')          3
['c:\\music\\_singles\\sidewinder.mp3',
'c:\\music\\_singles\\spinning.mp3']
>>> glob.glob('c:\\music\\*\\*.mp3')                  4
1 Como vimos anteriormente, os.listdir se limita a tomar una ruta de directorio y lista todos los ficheros y directorios que hay dentro.
2 El módulo glob, por otro lado, toma un comodín y devuelve la ruta absoluta hasta todos los ficheros y directorios que se ajusten al comodín. Aquí estamos usando una ruta de directorio más "*.mp3", que coincidirá con todos los ficheros .mp3. Observe que cada elemento de la lista devuelta incluye la ruta completa hasta el fichero.
3 Si quiere encontrar todos los ficheros de un directorio específico que empiecen con "s" y terminen en ".mp3", también puede hacerlo.
4 Ahora imagine esta situación: tiene un directorio music, con varios subdirectorios, con ficheros .mp3 dentro de cada uno. Podemos obtener una lista de todos ellos con una sola llamada a glob, usando dos comodines a la vez. Uno es el "*.mp3" (para capturar los ficheros .mp3), y el otro comodín está dentro de la propia ruta al directorio, para que capture todos los subdirectorios dentro de c:\music. ¡Una increíble cantidad de potencial dentro de una función de aspecto engañosamente simple!

Lecturas complementarias sobre el módulo os