Python: enumerar archivos en un directorio

    Prefiero trabajar con Python porque es un lenguaje de programaci贸n muy flexible y me permite interactuar f谩cilmente con el sistema operativo. Esto tambi茅n incluye funciones del sistema de archivos. Para simplemente enumerar archivos en un directorio, los m贸dulos os, subprocess, fnmatchy pathlib ven a jugar. Las siguientes soluciones demuestran c贸mo utilizar estos m茅todos de forma eficaz.

    Utilizando os.walk()

    los os El m贸dulo contiene una larga lista de m茅todos que tratan con el sistema de archivos y el sistema operativo. Uno de ellos es walk(), que genera los nombres de archivo en un 谩rbol de directorios recorriendo el 谩rbol de arriba hacia abajo o de abajo hacia arriba (siendo de arriba hacia abajo la configuraci贸n predeterminada).

    os.walk() devuelve una lista de tres elementos. Contiene el nombre del directorio ra铆z, una lista de los nombres de los subdirectorios y una lista de los nombres de archivo en el directorio actual. El Listado 1 muestra c贸mo escribir esto con solo tres l铆neas de c贸digo. Esto funciona con int茅rpretes de Python 2 y 3.

    Listado 1: Atravesando el directorio actual usando os.walk()

    import os
    
    for root, dirs, files in os.walk("."):
        for filename in files:
            print(filename)
    

    Uso de la l铆nea de comandos a trav茅s de subprocesos

    Nota: Si bien esta es una forma v谩lida de listar archivos en un directorio, no se recomienda ya que presenta la oportunidad de ataques de inyecci贸n de comandos.

    Como ya se describi贸 en el art铆culo Procesamiento paralelo en Python, el subprocess El m贸dulo le permite ejecutar un comando del sistema y recopilar su resultado. El comando del sistema que llamamos en este caso es el siguiente:

    Ejemplo 1: enumerar los archivos en el directorio actual

    $ ls -p . | grep -v /$
    

    El comando ls -p . enumera los archivos de directorio para el directorio actual y agrega el delimitador / al final del nombre de cada subdirectorio, que necesitaremos en el siguiente paso. La salida de esta llamada se canaliza al grep comando que filtra los datos seg煤n los necesitemos.

    Los parametros -v /$ excluir todos los nombres de las entradas que terminan con el delimitador /. Realmente, /$ es una expresi贸n regular que coincide con todas las cadenas que contienen el car谩cter / como el 煤ltimo car谩cter antes del final de la cadena, que est谩 representado por $.

    los subprocess El m贸dulo permite construir tuber铆as reales y conectar los flujos de entrada y salida como lo hace en una l铆nea de comando. Llamar al m茅todo subprocess.Popen() abre un proceso correspondiente y define los dos par谩metros denominados stdin y stdout.

    El Listado 2 muestra c贸mo programar eso. La primera variable ls se define como un proceso que ejecuta ls -p . que sale a una tuber铆a. Es por eso que el canal stdout se define como subprocess.PIPE. La segunda variable grep tambi茅n se define como un proceso, pero ejecuta el comando grep -v /$, en su lugar.

    Para leer la salida del ls comando desde la tuber铆a, el canal stdin de grep Se define como ls.stdout. Finalmente, la variable endOfPipe lee la salida de grep de grep.stdout que se imprime como elemento de salida est谩ndar en el for-bucle debajo. La salida se ve en el Ejemplo 2.

    Listado 2: Definici贸n de dos procesos conectados con una tuber铆a

    import subprocess
    
    # define the ls command
    ls = subprocess.Popen(["ls", "-p", "."],
                          stdout=subprocess.PIPE,
                         )
    
    # define the grep command
    grep = subprocess.Popen(["grep", "-v", "/$"],
                            stdin=ls.stdout,
                            stdout=subprocess.PIPE,
                            )
    
    # read from the end of the pipe (stdout)
    endOfPipe = grep.stdout
    
    # output the files line by line
    for line in endOfPipe:
        print (line)
    

    Ejemplo 2: ejecutar el programa

    $ python find-files3.py
    find-files2.py
    find-files3.py
    find-files4.py
    ...
    

    Esta soluci贸n funciona bastante bien con Python 2 y 3, pero 驴podemos mejorarla de alguna manera? Entonces, echemos un vistazo a las otras variantes.

    Combinatorio os y fnmatch

    Como ha visto antes, la soluci贸n que usa subprocesos es elegante pero requiere mucho c贸digo. En cambio, combinemos los m茅todos de los dos m贸dulos osy fnmatch. Esta variante tambi茅n funciona con Python 2 y 3.

    Como primer paso, importamos los dos m贸dulos osy fnmatch. A continuaci贸n, definimos el directorio en el que nos gustar铆a listar los archivos usando os.listdir(), as铆 como el patr贸n por qu茅 archivos filtrar. en un for bucle iteramos sobre la lista de entradas almacenadas en la variable listOfFiles.

    Finalmente, con la ayuda de fnmatch filtramos las entradas que estamos buscando e imprimimos las entradas coincidentes en stdout. El Listado 3 contiene el script Python y el Ejemplo 3 la salida correspondiente.

    Listado 3: Listado de archivos usando os y el m贸dulo fnmatch

    import os, fnmatch
    
    listOfFiles = os.listdir('.')
    pattern = "*.py"
    for entry in listOfFiles:
        if fnmatch.fnmatch(entry, pattern):
                print (entry)
    

    Ejemplo 3: la salida del Listado 3

    $ python2 find-files.py
    find-files.py
    find-files2.py
    find-files3.py
    ...
    

    Utilizando os.listdir() y generadores

    En t茅rminos simples, un generador es un poderoso iterador que mantiene su estado. Para obtener m谩s informaci贸n sobre los generadores, consulte uno de nuestros art铆culos anteriores, Python Generators.

    La siguiente variante combina el listdir() m茅todo del os m贸dulo con funci贸n de generador. El c贸digo funciona con las versiones 2 y 3 de Python.

    Como puede haber notado antes, el listdir() El m茅todo devuelve la lista de entradas para el directorio dado. El m茅todo os.path.isfile() devoluciones True si la entrada dada es un archivo. los yield El operador sale de la funci贸n pero mantiene el estado actual y devuelve solo el nombre de la entrada detectada como archivo. Esto nos permite recorrer la funci贸n del generador (ver Listado 4). La salida es id茅ntica a la del ejemplo 3.

    Listado 4: Combinando os.listdir() y una funci贸n de generador

    import os
    
    def files(path):
        for file in os.listdir(path):
            if os.path.isfile(os.path.join(path, file)):
                yield file
    
    for file in files("."):
        print (file)
    

    Utilizar pathlib

    los pathlib El m贸dulo se describe a s铆 mismo como una forma de “analizar, compilar, probar y de otro modo trabajar en nombres de archivo y rutas utilizando una API orientada a objetos en lugar de operaciones de cadena de bajo nivel”. Esto suena genial, hag谩moslo. A partir de Python 3, el m贸dulo pertenece a la distribuci贸n est谩ndar.

    En el Listado 5, primero definimos el directorio. El punto (“.”) Define el directorio actual. A continuaci贸n, el iterdir() El m茅todo devuelve un iterador que produce los nombres de todos los archivos. en un for bucle imprimimos el nombre de los archivos uno tras otro.

    Listado 5: Leyendo el contenido del directorio con pathlib

    import pathlib
    
    # define the path
    currentDirectory = pathlib.Path('.')
    
    for currentFile in currentDirectory.iterdir():
        print(currentFile)
    

    Nuevamente, la salida es id茅ntica a la del ejemplo 3.

    Como alternativa, podemos recuperar archivos haciendo coincidir sus nombres de archivo usando algo llamado glob. De esta forma solo podemos recuperar los archivos que queramos. Por ejemplo, en el c贸digo siguiente solo queremos enumerar los archivos de Python en nuestro directorio, lo que hacemos especificando “* .py” en el glob.

    Listado 6: Usando pathlib con el glob m茅todo

    import pathlib
    
    # define the path
    currentDirectory = pathlib.Path('.')
    
    # define the pattern
    currentPattern = "*.py"
    
    for currentFile in currentDirectory.glob(currentPattern):
        print(currentFile)
    

    Utilizando os.scandir()

    En Python 3.6, un nuevo m茅todo est谩 disponible en el os m贸dulo. Se llama scandir()y simplifica significativamente la llamada a listar archivos en un directorio.

    Habiendo importado el os m贸dulo primero, use el getcwd() m茅todo para detectar el directorio de trabajo actual y guardar este valor en el path variable. Siguiente, scandir() devuelve una lista de entradas para esta ruta, que probamos para ser un archivo usando el is_file() m茅todo.

    Listado 7: Leyendo el contenido del directorio con scandir()

    import os
    
    # detect the current working directory
    path = os.getcwd()
    
    # read the entries
    with os.scandir(path) as listOfEntries:
        for entry in listOfEntries:
            # print all entries that are files
            if entry.is_file():
                print(entry.name)
    

    Nuevamente, la salida del Listado 7 es id茅ntica a la del Ejemplo 3.

    Conclusi贸n

    Hay desacuerdo sobre cu谩l es la mejor versi贸n, cu谩l es la m谩s elegante y cu谩l es la m谩s “pythonica”. Me gusta la sencillez del os.walk() m茅todo, as铆 como el uso de ambos fnmatch y pathlib m贸dulos.

    Las dos versiones con los procesos / tuber铆as y el iterador requieren una comprensi贸n m谩s profunda de los procesos de UNIX y el conocimiento de Python, por lo que pueden no ser las mejores para todos los programadores debido a su complejidad adicional (e innecesaria).

    Para encontrar una respuesta a qu茅 versi贸n es la m谩s r谩pida, el timeit El m贸dulo es bastante 煤til. Este m贸dulo cuenta el tiempo que ha transcurrido entre dos eventos.

    Para comparar todas nuestras soluciones sin modificarlas, usamos una funcionalidad de Python: llame al int茅rprete de Python con el nombre del m贸dulo y el c贸digo de Python apropiado que se ejecutar谩. Para hacer eso para todos los scripts de Python a la vez, un script de shell ayuda (Listado 8).

    Listado 8: Evaluar el tiempo de ejecuci贸n usando el timeit m贸dulo

    #! /bin/bash
    
    for filename in *.py; do
        echo "$filename:"
        cat $filename | python3 -m timeit
        echo " "
    done
    

    Las pruebas se realizaron con Python 3.5.3. El resultado es el siguiente, mientras que os.walk() da el mejor resultado. Ejecutar las pruebas con Python 2 devuelve valores diferentes pero no cambia el orden – os.walk() todav铆a est谩 en la parte superior de la lista.

    Resultado del m茅todo para 100,000,000 bucles

    os.walk0.0085 usec por bucle
    subproceso / tuber铆a0.00859 usec por bucle
    os.listdir / fnmatch0.00912 usec por bucle
    os.listdir / generator0.00867 usec por bucle
    pathlib0.00854 usec por bucle
    pathlib / glob0.00858 usec por bucle
    os.scandir0.00856 usec por bucle

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *