¿Cómo determinar correctamente el directorio de script actual?

261

Me gustaría ver cuál es la mejor manera de determinar el directorio de script actual en python.

Descubrí que, debido a las muchas formas de llamar al código de Python, es difícil encontrar una buena solución.

Aquí hay algunos problemas:

  • __file__no está definido si el script se ejecuta con exec,execfile
  • __module__ se define solo en módulos

Casos de uso:

  • ./myfile.py
  • python myfile.py
  • ./somedir/myfile.py
  • python somedir/myfile.py
  • execfile('myfile.py') (desde otro script, que puede ubicarse en otro directorio y que puede tener otro directorio actual.

Sé que no hay una solución perfecta, pero estoy buscando el mejor enfoque que resuelva la mayoría de los casos.

El enfoque más utilizado es, os.path.dirname(os.path.abspath(__file__))pero esto realmente no funciona si ejecuta el script desde otro con exec().

Advertencia

Cualquier solución que use el directorio actual fallará, esto puede ser diferente según la forma en que se llama el script o se puede cambiar dentro del script en ejecución.

Bogdan
fuente
1
¿Puede ser más específico donde necesita saber de dónde viene el archivo? - en el código que importa el archivo (host con inclusión) o en el archivo que se importa? (esclavo consciente de sí mismo)
sintetizadorpatel
3
Vea la pathlibsolución de Ron Kalian si está utilizando Python 3.4 o superior: stackoverflow.com/a/48931294/1011724
Dan
Entonces, ¿la solución NO es usar ningún directorio actual en el código, sino usar algún archivo de configuración?
ZhaoGang
Descubrimiento interesante, acabo de hacer: cuando lo hago python myfile.pydesde un shell, funciona, pero ambos :!python %y :!python myfile.pydesde vim fallan con El sistema no puede encontrar la ruta especificada. Esto es bastante molesto. ¿Alguien puede comentar sobre la razón detrás de esto y las posibles soluciones?
inVader

Respuestas:

231
os.path.dirname(os.path.abspath(__file__))

es de hecho lo mejor que vas a conseguir.

Es inusual ejecutar un script con exec/ execfile; normalmente debería usar la infraestructura del módulo para cargar scripts. Si debe utilizar estos métodos, le sugiero que configure __file__el globalspaso al script para que pueda leer ese nombre de archivo.

No hay otra forma de obtener el nombre de archivo en el código ejecutado: como se observa, el CWD puede estar en un lugar completamente diferente.

bobince
fuente
2
¿Nunca digas nunca? De acuerdo con esto: stackoverflow.com/a/18489147 responde una solución multiplataforma es abspath (getsourcefile (lambda: 0))? ¿O hay algo más que me estoy perdiendo?
Jeff Ellen
131

Si realmente desea cubrir el caso por el que se llama un script execfile(...), puede usar el inspectmódulo para deducir el nombre de archivo (incluida la ruta). Hasta donde yo sé, esto funcionará para todos los casos que enumeró:

filename = inspect.getframeinfo(inspect.currentframe()).filename
path = os.path.dirname(os.path.abspath(filename))
Sven Marnach
fuente
44
Creo que este es el método más robusto, pero cuestiono la necesidad declarada del OP para esto. A menudo veo que los desarrolladores hacen esto cuando usan archivos de datos en ubicaciones relativas al módulo de ejecución, pero los archivos de datos IMO deben colocarse en una ubicación conocida.
Ryan Ginstrom
14
@Ryan LOL, sería genial si pudieras definir una "ubicación conocida" que sea multiplataforma y que también venga con el módulo. Estoy listo para apostar que la única ubicación segura es la ubicación del script. Tenga en cuenta que esto no significa que el script deba escribir en esta ubicación, pero para leer datos es seguro.
sorin
1
Aún así, la solución no es buena, solo intente llamar chdir()antes de la función, cambiará el resultado. También llamar al script de Python desde otro directorio alterará el resultado, por lo que no es una buena solución.
sorin
2
os.path.expanduser("~")es una forma multiplataforma para obtener el directorio del usuario. Desafortunadamente, esa no es la mejor práctica de Windows sobre dónde pegar los datos de la aplicación.
Ryan Ginstrom
66
@sorin: lo he intentado chdir()antes de ejecutar el script; Produce el resultado correcto. Intenté llamar al script desde otro directorio y también funciona. Los resultados son los mismos que para la inspect.getabsfile()solución basada .
jfs
43
#!/usr/bin/env python
import inspect
import os
import sys

def get_script_dir(follow_symlinks=True):
    if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze
        path = os.path.abspath(sys.executable)
    else:
        path = inspect.getabsfile(get_script_dir)
    if follow_symlinks:
        path = os.path.realpath(path)
    return os.path.dirname(path)

print(get_script_dir())

Funciona en CPython, Jython, Pypy. Funciona si el script se ejecuta usando execfile()( sys.argv[0]y las __file__soluciones basadas fallarían aquí). Funciona si el script está dentro de un archivo zip ejecutable (/ an egg) . Funciona si el script se "importa" ( PYTHONPATH=/path/to/library.zip python -mscript_to_run) desde un archivo zip; devuelve la ruta del archivo en este caso. Funciona si el script se compila en un ejecutable independiente ( sys.frozen). Funciona para enlaces simbólicos ( realpathelimina enlaces simbólicos). Funciona en un intérprete interactivo; devuelve el directorio de trabajo actual en este caso.

jfs
fuente
Funciona perfectamente bien con PyInstaller.
gaborous
1
¿Hay alguna razón por la cual getabsfile(..)no se menciona en la documentacióninspect ? Aparece en la fuente que está vinculada desde esa página.
Evgeni Sergeev
@EvgeniSergeev podría ser un error. Es un simple envoltorio alrededor getsourcefile(), getfile()que están documentados.
jfs
24

En Python 3.4+ puedes usar el pathlibmódulo más simple :

from inspect import currentframe, getframeinfo
from pathlib import Path

filename = getframeinfo(currentframe()).filename
parent = Path(filename).resolve().parent
Eugene Yarmash
fuente
2
Excelente simplicidad!
Cometsong
3
Probablemente pueda usar Path(__file__)(no necesita el inspectmódulo).
Peque
@Peque haciendo eso produce una ruta que incluye el nombre del archivo actual, no el directorio principal. Si estoy tratando de obtener el directorio de script actual para apuntar a un archivo en el mismo directorio, por ejemplo, esperando cargar un archivo de configuración en el mismo directorio que el script, Path(__file__)da /path/to/script/currentscript.pycuando el OP quería obtener/path/to/script/
Davos
8
Oh, no entendí bien, quieres evitar el módulo de inspección y usar algo como parent = Path(__file__).resolve().parent Eso es mucho mejor.
Davos
3
@Dut A. Debe usar .joinpath()(o el /operador) para esto, no +.
Eugene Yarmash
13

El os.path...enfoque fue lo 'hecho' en Python 2.

En Python 3, puede encontrar el directorio del script de la siguiente manera:

from pathlib import Path
cwd = Path(__file__).parents[0]
Ron Kalian
fuente
11
O simplemente Path(__file__).parent. Pero cwdes un nombre inapropiado, ese no es el directorio de trabajo actual , sino el directorio del archivo . Podrían ser lo mismo, pero ese no suele ser el caso.
Nuno André
5

Simplemente use os.path.dirname(os.path.abspath(__file__))y examine con mucho cuidado si existe una necesidad real del caso en el que execse usa. Podría ser un signo de diseño problemático si no puede usar su script como módulo.

Tenga en cuenta Zen of Python # 8 , y si cree que hay un buen argumento para un caso de uso en el que debe funcionar exec, háganos saber algunos detalles más sobre el fondo del problema.

wim
fuente
2
Si no se ejecuta con exec (), perderá el contexto del depurador. También se supone que exec () es considerablemente más rápido que comenzar un nuevo proceso.
Sorin
@sorin No es una cuestión de ejecutivo versus comenzar un nuevo proceso, así que ese es un argumento de sorpresa. Es una cuestión de ejecutivo versus usar una llamada de importación o función.
wim
4

haría

import os
cwd = os.getcwd()

¿Haz lo que quieras? No estoy seguro de qué quiere decir exactamente con el "directorio de script actual". ¿Cuál sería el resultado esperado para los casos de uso que proporcionó?

Will McCutchen
fuente
3
No ayudaría Creo que @bogdan está buscando el directorio para el script que está en la parte superior de la pila de llamadas. es decir, en todos sus casos, debe imprimir el directorio donde se encuentra 'myfile.py'. Sin embargo, su método solo imprimirá el directorio del archivo que llama exec('myfile.py'), igual que __file__y sys.argv[0].
Zhang18
Sí, eso tiene sentido. Solo quería asegurarme de que @bogdan no pasara por alto algo simple, y no podía decir exactamente lo que querían.
Will McCutchen
3

Primero ... un par de casos de uso faltantes aquí si estamos hablando de formas de inyectar código anónimo ...

code.compile_command()
code.interact()
imp.load_compiled()
imp.load_dynamic()
imp.load_module()
__builtin__.compile()
loading C compiled shared objects? example: _socket?)

Pero, la verdadera pregunta es, ¿cuál es su objetivo? ¿Está tratando de imponer algún tipo de seguridad? ¿O simplemente está interesado en lo que se está cargando?

Si está interesado en la seguridad , el nombre de archivo que se importa a través de exec / execfile es intrascendente; debe usar rexec , que ofrece lo siguiente:

Este módulo contiene la clase RExec, que admite los métodos r_eval (), r_execfile (), r_exec () y r_import (), que son versiones restringidas de las funciones estándar de Python eval (), execfile () y las instrucciones exec e import. El código ejecutado en este entorno restringido solo tendrá acceso a módulos y funciones que se consideren seguras; puede subclasificar RExec para agregar o quitar capacidades según lo desee.

Sin embargo, si esto es más una búsqueda académica ... aquí hay un par de enfoques tontos en los que podrías profundizar un poco más ...

Guiones de ejemplo:

./deep.py

print ' >> level 1'
execfile('deeper.py')
print ' << level 1'

./deeper.py

print '\t >> level 2'
exec("import sys; sys.path.append('/tmp'); import deepest")
print '\t << level 2'

/tmp/deepest.py

print '\t\t >> level 3'
print '\t\t\t I can see the earths core.'
print '\t\t << level 3'

./codespy.py

import sys, os

def overseer(frame, event, arg):
    print "loaded(%s)" % os.path.abspath(frame.f_code.co_filename)

sys.settrace(overseer)
execfile("deep.py")
sys.exit(0)

Salida

loaded(/Users/synthesizerpatel/deep.py)
>> level 1
loaded(/Users/synthesizerpatel/deeper.py)
    >> level 2
loaded(/Users/synthesizerpatel/<string>)
loaded(/tmp/deepest.py)
        >> level 3
            I can see the earths core.
        << level 3
    << level 2
<< level 1

Por supuesto, esta es una forma intensiva de recursos para hacerlo, estaría rastreando todo su código ... No es muy eficiente. Pero, creo que es un enfoque novedoso ya que continúa funcionando incluso a medida que profundizas en el nido. No puede anular 'eval'. Aunque tú puedes anular execfile ().

Tenga en cuenta que este enfoque solo codicia exec / execfile, no 'import'. Para un enganche de carga de 'módulo' de nivel superior, puede usar use sys.path_hooks (Escritura cortesía de PyMOTW).

Eso es todo lo que tengo fuera de mi cabeza.

sintetizadorpatel
fuente
2

Aquí hay una solución parcial, aún mejor que todas las publicadas hasta ahora.

import sys, os, os.path, inspect

#os.chdir("..")

if '__file__' not in locals():
    __file__ = inspect.getframeinfo(inspect.currentframe())[0]

print os.path.dirname(os.path.abspath(__file__))

Ahora esto funciona todas las llamadas, pero si alguien usa chdir()para cambiar el directorio actual, también fallará.

Notas:

  • sys.argv[0]no va a funcionar, volverá -csi ejecuta el script conpython -c "execfile('path-tester.py')"
  • Publiqué una prueba completa en https://gist.github.com/1385555 y puede mejorarla.
Sorin
fuente
1

Esto debería funcionar en la mayoría de los casos:

import os,sys
dirname=os.path.dirname(os.path.realpath(sys.argv[0]))
Jahid
fuente
55
Esta solución usa el directorio actual y se dice explícitamente en la pregunta que dicha solución fallará.
Skyking
1

Esperemos que esto ayude: - Si ejecuta un script / módulo desde cualquier lugar, podrá acceder a la __file__variable que es una variable de módulo que representa la ubicación del script.

Por otro lado, si está utilizando el intérprete, no tiene acceso a esa variable, donde obtendrá un nombre NameErroryos.getcwd() le dará el directorio incorrecto si está ejecutando el archivo desde otro lugar.

Esta solución debería darle lo que está buscando en todos los casos:

from inspect import getsourcefile
from os.path import abspath
abspath(getsourcefile(lambda:0))

No lo he probado a fondo, pero resolvió mi problema.

marca
fuente
Esto dará archivo, no directorio
Shital Shah