Stack Overflow en español Asked by user166844 on August 26, 2021
El caso es que tengo una estructura de archivos, y desarrollé un algoritmo de búsqueda de un archivo para estructura multi-directorio. La estructura es la siguiente:
path
dir0
dir2
file3
dir1
file2
file0
file1
Estoy usando esta función:
def searchfile(paths: list,search: str):
for path in paths:
print("Directorio actual: {}".format(path))
contents = os.listdir(path)
print("Contenidos: {}".format(contents))
if search in contents:
print("Archivo encontrado! n")
return "{}".format(os.path.abspath(search))
sys.exit()
dirs = ["{}{}/".format(path,x) for x in contents if os.path.isdir("{}/{}/".format(path,x)) == True]
print("Directorios: {} n".format(dirs))
searchfile(dirs,search)
Como se puede ver, emplea una llamada recursiva dentro de la propia definición de función.
¿Por que uso una llamada recursiva?
Debido a que esta función se debe de adaptar a estructura de una longitud indefinida, es decir que a una estructura que puede tener una cantidad indefinida de directorios dentro de un directorio, y esos directorios una cantidad indefinida de directorios dentro de cada uno. El algoritmo debe de ser capaz de realizar una búsqueda en todos los directorios de estructura.
dir
dir
dir
dir
...
dir
dir
dir
dir
..
dir
dir
Donde "dir" es un directorio X
Lo que pasa es que la única manera que encontré de iterar sobre el contenido de cada directorio, es invocando la misma función en cada elemento de la lista generada por dirs
. Entonces, de esa manera se ejecutará la función hasta que la distancia a un un punto nulo se igual a 0. En esto se define un punto nulo como un directorio que solo contenga archivos o bien que esté vació. Si el directorio solo contiene archivos el filtro que realiza la expresión generadora de dirs
equivaldrá a []
, por lo tanto la siguiente llamada de la función será una iteración sobre una lista vacía (osea nula).
¿Dónde está el problema?
El condicional que uso para validar si el archivo se encontró (100% de eficacia):
if search in contents:
print("Archivo encontrado! n")
return "{}".format(os.path.abspath(search))
sys.exit()
Se ejecuta, pero no cumple mi objetivo. Este objetivo es terminar el programa con sys.exit()
. El problema surge ya que se realizan llamadas recursivas dentro de una iteración es decir que a lo mejor una llamada a la función puede evaluar un directorio, y por más que el archivo esté en ese directorio siempre va aquedar la otra iteración pendiente. Ocurre algo así:
dir*
dir*
dir*
busqueda
dir
dir
...
Donde "busqueda" es el archivo a encontrar
Como se puede ver los directorios que el tienen asterisco es la ruta sobre la que se está iterando, aunque se llegue a "busqueda" va a quedar pendiente evaluar los directorios sin el asterisco.
¿Cómo se sabe con certeza que el condicional si se ejecuta?
Al ejecutar la función con un ejemplo
print(searchfile(["path/"],"file3.txt"))
obtengo
Directorio actual: path/
Contenidos: ['dir0', 'dir1', 'file0.txt', 'file1.txt']
Directorios: ['path/dir0/', 'path/dir1/']
Directorio actual: path/dir0/
Contenidos: ['dir2']
Directorios: ['path/dir0/dir2/']
Directorio actual: path/dir0/dir2/
Contenidos: ['file3.txt']
Archivo encontrado!
Directorio actual: path/dir1/
Contenidos: ['file2.txt']
Directorios: []
None
Se puede notar clarmente que aparece el mensaje de Archivo encontrado! y no se muestra el contenido del directorio. Esto deja como conclusión que el condicional si que se está ejecutando y el sys.exit()
solo tiene influencia sobre esa iteración, no sobre todo el bucle y mucho menos sobre todo el programa.
Otra cosa a notar es que retorna None
, osea que la linea return "{}".format(os.path.abspath(search))
no tiene efecto. Ni siquiera por que la casteo a un string.
Leyendo la referencia en la documentación de Python:
Exit from Python. This is implemented by raising the SystemExit
exception, so cleanup actions specified by finally clauses of try
statements are honored, and it is possible to intercept the exit
attempt at an outer level.
Dice que claramente "Exit from Python". Entonces me surgen varias preguntas.
sys.exit()
no tiene influencia sobre todo el programa (o función)?sys.exit()
es adecuada para este tipo de casos?Agradecería cualquier explicación o comentario. Muchas gracias de antemano, saludos.
Por lo visto el programa no se está deteniendo debido a que existe un return
antes del sys.exit()
. Esto provoca que dicha función (sys.exit()
) no se ejecute, ya que return
da como resultado el final de una función.
Al tratarse de una invocación dentro de un bucle for
tiene sentido que el programa no parara. Entonces me surge esta pregunta:
¿Existe una forma de detener la función y retornar un valor?
Ahora el verdadero problema surge con las iteraciones y lo que implican:
Si hay un return
significa el final de la función, pero al tratarse de una invocación en un ciclo… Siempre se va ejecutar la otra. Además el valor se va a perder y va terminar siendo None
, por que la función que se invoca es la que se espera que retorne un valor, no las invocadas a base de esta.
Si hay un sys.exit()
después del retorno de la función no se ejecutará, si no el siguiente ciclo.
Pasa lo mismo que con sys.exit()
, no se llegará a ejecutar. Por otro lado, si fuera posible no rompería el ciclo. Esto por que se está hablando del ciclo de una función que invoca a la función actual. Son ciclos for
anidados por funciones, pasaría algo así:
funcion():
for i in iterable:#1
funcion():
for i in iterable: #2
funcion():
...
for i in iterable: #X
if condicion:
break
Como se puede ver, el break
del ciclo for #X
solo rompería este mismo ciclo. No tendría la capacidad de romperá ciclo en la función que esta invocando la función de dicho break
. Se queda corto.
No importa de que manera si se usa return
o break
para acabar la función o ciclo, tampoco si se usa sys.exit()
ya que no se puede obtener un valor por la función si se usa.
Se puede llegar a la conclusión de que el problema no está en ¿Cómo detener el programa?, si no más bien en el ¿Cómo obtener el el valor rertornado de una llamada recursiva y usarlo como valor de retorno de la función "padre"?
Por ende, se puede saber la manera adecuada es usando return
sin más, esto significa detener la función actual y obtener el valor en cuestión. Por eso mismo intenté especificar que el valor de la llamada a la función dentro de la función era el valor de la función:
def searchfile(paths: list,search: str):
for path in paths:
print("Actual path: {}".format(path))
contents = os.listdir(path)
print("Contents: {}".format(contents))
if search in contents:
print("File found! n")
return os.path.abspath(search)
dirs = ["{}{}/".format(path,x) for x in contents if os.path.isdir("{}/{}/".format(path,x)) == True]
print("Directories: {} n".format(dirs))
return searchfile(dirs,search)
Esto suena muy bien, debido a que funciona con estructuras de archivos con longitud indefinida. Esto debido a que este retorno es recursivo. Cuando intento con:
print(searchfile(["path/"],"file3.txt"))
Obtengo:
Actual path: path/
Contents: ['dir0', 'dir1', 'file0.txt', 'file1.txt']
Directories: ['path/dir0/', 'path/dir1/']
Actual path: path/dir0/
Contents: ['dir2']
Directories: ['path/dir0/dir2/']
Actual path: path/dir0/dir2/
Contents: ['file3.txt']
File found!
C:UsersaliacDesktopfile3.txt
Un resultado "correcto". Sin embargo a la hora de intentar con un archivo en otra ubicación.
print(searchfile(["path/"],"file2.txt"))
Obtengo:
Actual path: path/
Contents: ['dir0', 'dir1', 'file0.txt', 'file1.txt']
Directories: ['path/dir0/', 'path/dir1/']
Actual path: path/dir0/
Contents: ['dir2']
Directories: ['path/dir0/dir2/']
Actual path: path/dir0/dir2/
Contents: ['file3.txt']
Directories: []
None
Esto deja en evidencia dos cosas:
Solo se está iterando sobre el primer elemento de la lista argumento path
en cada llamada. Pasa algo como esto:
func()
func()
func()
func($)
func($)
Las funciones representadas con func() que tiene como argumento $
son las que nunca se van a ejecutar. Por que cada función "hijo" (funciones llamadas por otras funciones), van a retornar un valor y con mi intento de código significa que el el "padre" (función que invoca a otra función) va a retornar un valor. Entonces una función que tenga que hacer llamadas recursivas a otra función por medio de una iteración solo va a llamar a la primera iteración, ya que return
significa el final de una función. Si hay un final en la primera llamada va a haber un final en la función que la llama (según mi intento)
None
Nunca encuentra el archivo si está en una ubicación que no corresponde al primer subdirectorio del "directorio actual" (sobre el que se está iterando). Esto ocurre por la primera razón, visualizando;
dir #
sdir #
sdir #
...
sdir
sdir
sdir
sdir
Donde los dir o sdir que tenga una almohadilla (#) son los que la función tomará en cuenta, en cualquier caso. Esto ocurre básicamente por el problema del retorno en la primera iteración.
dirC[0] => sdirC[0] => sdirC[0] ... sdirC[0]
La C al final de cada elemento para representar el contenido de dicho elemento
Esto da la conclusión de que mi intento no es válido.
Entonces:
Sinceramente no sé si me dí a entender, no soy muy bueno explicándome. Si la pregunta está mal planteada, agradecería una corrección.
Hay varias consideraciones:
Una instrucción que sigue a un return
no se ejecuta nunca.
return "{}".format(os.path.abspath(search))
sys.exit()
El sys.exit()
es como si no estuviera; puedes borrarlo.
Estas llamando recursivamente sin recuperar el valor que la llamada puede haber generado. Si encuentras el archivo en un subdirectorio, su nombre simplemente se pierde.
La verdad es que para este tipo de operaciones sobre directorios, el algoritmo general es más simple: se toma cada entrada del directorio. Si es el archivo buscado, se retorna. Si es un directorio, se invoca recursivamente la función y luego se examina el resultado. Si lo buscado, se retorna con eso.
Simplifique tambièn la llamada. No es necesario pasar una lista de directorios.
En resumen:
import os
def searchfile(path: str, search: str):
for f in os.listdir(path):
item=os.path.join(path, f)
if os.path.isfile(item) and f == search:
break
elif os.path.isdir(item):
item = searchfile(item,search)
if item:
break
else:
item = None
return item
print(searchfile("/home/candid/bin/", "fonts.css"))
produce
candid@dell ~ $ python3 dir.py
/home/candid/bin/arduino-1.8.5/reference/www.arduino.cc/fonts/fonts.css
a
Edit
En general, es mejor escribir métodos que hagan una sola cosa, la mínima posible, pues es más fácil agregar que quitar. También es más fácil combinar que dividir.
En este caso, la función busca en un solo directorio, pero ¿Qué pasa si uno quiere revisar varios directorios no relacionados?
La solución es hacer una llamada por directorio:
directorios = ["dir1", "dir2", ..., "dirN"]
for dir in directorios:
archivo = searchfile(dir, nombre_archivo)
if archivo:
... hacer algo con el archivo ...
o también puedes armar una lista con los archivos encontrados:
lista_archivos = [searchfile(dir, nombre_archivo) for dir in directorios]
o incluso crear otra función:
def busca_en_dirs(lista_dir, nombre_archivo):
return [searchfile(dir, nombre_archivo) for dir in lista_dir]
Answered by Candid Moe on August 26, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP