Cómo abrir de manera confiable un archivo en el mismo directorio que un script Python

157

Solía ​​abrir archivos que estaban en el mismo directorio que el script de Python actualmente en ejecución simplemente usando un comando como

open("Some file.txt", "r")

Sin embargo, descubrí que cuando el script se ejecutaba en Windows haciendo doble clic en él, intentaba abrir el archivo desde el directorio incorrecto.

Desde entonces he usado un comando de la forma

open(os.path.join(sys.path[0], "Some file.txt"), "r")

cada vez que quería abrir un archivo. Esto funciona para mi uso particular, pero no estoy seguro de si sys.path[0]podría fallar en algún otro caso de uso.

Entonces mi pregunta es: ¿Cuál es la mejor y más confiable forma de abrir un archivo que se encuentra en el mismo directorio que el script Python actualmente en ejecución?

Esto es lo que he podido descubrir hasta ahora:

  • os.getcwd()y os.path.abspath('')devuelve el "directorio de trabajo actual", no el directorio del script.

  • os.path.dirname(sys.argv[0])y os.path.dirname(__file__)devuelve la ruta utilizada para llamar al script, que puede ser relativa o incluso en blanco (si el script está en el cwd). Además, __file__no existe cuando el script se ejecuta en IDLE o PythonWin.

  • sys.path[0]y os.path.abspath(os.path.dirname(sys.argv[0]))parece que devuelve el directorio del script. No estoy seguro de si hay alguna diferencia entre estos dos.

Editar:

Me acabo de dar cuenta de que lo que quiero hacer se describirá mejor como "abrir un archivo en el mismo directorio que el módulo contenedor". En otras palabras, si importo un módulo que escribí que está en otro directorio, y ese módulo abre un archivo, quiero que busque el archivo en el directorio del módulo. No creo que nada de lo que he encontrado sea capaz de hacer eso ...

dln385
fuente

Respuestas:

199

Yo siempre uso:

__location__ = os.path.realpath(
    os.path.join(os.getcwd(), os.path.dirname(__file__)))

La join()llamada antepone el directorio de trabajo actual, pero la documentación dice que si alguna ruta es absoluta, todas las demás rutas restantes quedan descartadas. Por lo tanto, getcwd()se descarta cuando dirname(__file__)devuelve una ruta absoluta.

Además, la realpathllamada resuelve enlaces simbólicos si se encuentra alguno. Esto evita problemas al implementar con herramientas de configuración en sistemas Linux (los scripts están vinculados a /usr/bin/, al menos en Debian).

Puede utilizar lo siguiente para abrir archivos en la misma carpeta:

f = open(os.path.join(__location__, 'bundled-resource.jpg'));
# ...

¡Utilizo esto para agrupar recursos con varias aplicaciones de Django en Windows y Linux y funciona de maravilla!

André Caron
fuente
44
Si __file__no se puede usar, use en sys.argv[0]lugar de dirname(__file__). El resto debería funcionar como se esperaba. Me gusta usarlo __file__porque en el código de la biblioteca, es sys.argv[0]posible que no apunte a su código, especialmente si se importa a través de un script de terceros.
André Caron
1
El problema con esto es que variará si el archivo que se está ejecutando proviene directamente del interruptor o si se importa. Vea mi respuesta para las diferencias entre file y sys.argv [0]
Zimm3r
Entonces, ¿es correcto decir que la variación descrita en la respuesta de Zimm3r se aborda utilizando realpath( join( getcwd(), dirname(__file__) ))como se describe aquí?
pianoJames
44

Para citar de la documentación de Python:

Como se inicializó al iniciar el programa, el primer elemento de esta lista, ruta [0], es el directorio que contiene el script que se utilizó para invocar al intérprete de Python. Si el directorio del script no está disponible (por ejemplo, si el intérprete se invoca de forma interactiva o si el script se lee desde la entrada estándar), la ruta [0] es la cadena vacía, que dirige a Python a buscar los módulos en el directorio actual primero. Observe que el directorio del script se inserta antes de las entradas insertadas como resultado de PYTHONPATH.

sys.path [0] es lo que estás buscando.

MONO ROJO
fuente
10
Y para la ruta completa del archivo: os.path.join(sys.path[0], 'some file.txt'). Eso debería manejar espacios y barras correctamente en todos los sistemas.
Jacktose
Esta respuesta a la primera pregunta, no la que sigue a EDIT.
mcoolive
22

Ok esto es lo que hago

sys.argv es siempre lo que escribe en el terminal o utiliza como ruta de archivo al ejecutarlo con python.exe o pythonw.exe

Por ejemplo, puede ejecutar el archivo text.py de varias maneras, cada una de ellas le da una respuesta diferente, siempre le dan la ruta de acceso que escribió Python.

    C:\Documents and Settings\Admin>python test.py
    sys.argv[0]: test.py
    C:\Documents and Settings\Admin>python "C:\Documents and Settings\Admin\test.py"
    sys.argv[0]: C:\Documents and Settings\Admin\test.py

Ok, así que sé que puedes obtener el nombre del archivo, gran cosa, ahora para obtener el directorio de la aplicación puedes saber usar os.path, específicamente abspath y dirname

    import sys, os
    print os.path.dirname(os.path.abspath(sys.argv[0]))

Eso generará esto:

   C:\Documents and Settings\Admin\

siempre generará esto sin importar si escribe python test.py o python "C: \ Documents and Settings \ Admin \ test.py"

El problema con el uso de __file__ Considere estos dos archivos test.py

import sys
import os

def paths():
        print "__file__: %s" % __file__
        print "sys.argv: %s" % sys.argv[0]

        a_f = os.path.abspath(__file__)
        a_s = os.path.abspath(sys.argv[0])

        print "abs __file__: %s" % a_f
        print "abs sys.argv: %s" % a_s

if __name__ == "__main__":
    paths()

import_test.py

import test
import sys

test.paths()

print "--------"
print __file__
print sys.argv[0]

Salida de "python test.py"

C:\Documents and Settings\Admin>python test.py
__file__: test.py
sys.argv: test.py
abs __file__: C:\Documents and Settings\Admin\test.py
abs sys.argv: C:\Documents and Settings\Admin\test.py

Salida de "python test_import.py"

C:\Documents and Settings\Admin>python test_import.py
__file__: C:\Documents and Settings\Admin\test.pyc
sys.argv: test_import.py
abs __file__: C:\Documents and Settings\Admin\test.pyc
abs sys.argv: C:\Documents and Settings\Admin\test_import.py
--------
test_import.py
test_import.py

Entonces, como puede ver, el archivo le proporciona siempre el archivo python desde el que se ejecuta, donde sys.argv [0] le proporciona siempre el archivo que ejecutó desde el intérprete. Dependiendo de sus necesidades, tendrá que elegir cuál se adapta mejor a sus necesidades.

Zimm3r
fuente
3
Esta es una prueba elaborada de que la implementación refleja la documentación. __file__se supone que "siempre le da la ruta al archivo actual", y sys.argv[0]se supone que "siempre le da la ruta del script que inició el proceso". En cualquier caso, usar __file__en el script que se invoca siempre te da resultados precisos.
André Caron
Si tiene la referencia __file__en el nivel superior del script, funcionará como se esperaba.
Matthew Schinckel
-1

Pude usar el código proporcionado por dcolish con éxito ya que tenía un problema similar al leer un archivo de texto específico. El archivo no está en el mismo cwd que el archivo Python.

Jaclyn Horton
fuente
1
Por favor no agregue "gracias" como respuesta. Una vez que tenga suficiente reputación , podrá votar las preguntas y respuestas que considere útiles. - De la opinión
Roberto Caboni
-3

Lo haría de esta manera:

from os.path import abspath, exists

f_path = abspath("fooabar.txt")

if exists(f_path):
    with open(f_path) as f:
        print f.read()

El código anterior crea una ruta absoluta al archivo usando abspath y es equivalente a usar normpath(join(os.getcwd(), path))[eso es de los pydocs]. Luego verifica si ese archivo realmente existe y luego usa un administrador de contexto para abrirlo, de modo que no tenga que acordarse de llamar al cierre en el identificador de archivo. En mi humilde opinión, hacerlo de esta manera te ahorrará mucho dolor a largo plazo.

decolorar
fuente
Esto no responde a la pregunta del cartel. dln385 dijo específicamente que os.path.abspathno resuelve las rutas a los archivos en la misma carpeta que el script si el script no está en el directorio actual.
André Caron
AH! Asumí que el usuario estaba ejecutando este script en el mismo directorio que el archivo que quería leer, NO en el directorio del módulo de algo en su PYTHONPATH. Eso me enseñará a hacer suposiciones ...
dcolish
abspath no funcionará, ya que es imposible que Python Runtime busque en el sistema de archivos del sistema operativo utilizando una función como esta.
akshat thakar