Recientemente he intentado hacer algo similar y he encontrado que estas respuestas son inadecuadas para mis casos de uso (una biblioteca distribuida que necesita detectar la raíz del proyecto). Principalmente he estado luchando contra diferentes entornos y plataformas, y todavía no he encontrado algo perfectamente universal.
Código local para proyectar
He visto este ejemplo mencionado y utilizado en algunos lugares, Django, etc.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Por simple que sea, solo funciona cuando el archivo en el que se encuentra el fragmento es realmente parte del proyecto. No recuperamos el directorio del proyecto, sino el directorio del fragmento
De manera similar, el enfoque sys.modules se rompe cuando se llama desde fuera del punto de entrada de la aplicación, específicamente, he observado que un hilo secundario no puede determinar esto sin relación con el módulo ' principal '. He puesto explícitamente la importación dentro de una función para demostrar una importación desde un hilo secundario, moverla al nivel superior de app.py lo solucionaría.
app/
|-- config
| `-- __init__.py
| `-- settings.py
`-- app.py
app.py
#!/usr/bin/env python
import threading
def background_setup():
# Explicitly importing this from the context of the child thread
from config import settings
print(settings.ROOT_DIR)
# Spawn a thread to background preparation tasks
t = threading.Thread(target=background_setup)
t.start()
# Do other things during initialization
t.join()
# Ready to take traffic
settings.py
import os
import sys
ROOT_DIR = None
def setup():
global ROOT_DIR
ROOT_DIR = os.path.dirname(sys.modules['__main__'].__file__)
# Do something slow
La ejecución de este programa produce un error de atributo:
>>> import main
>>> Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python2714\lib\threading.py", line 801, in __bootstrap_inner
self.run()
File "C:\Python2714\lib\threading.py", line 754, in run
self.__target(*self.__args, **self.__kwargs)
File "main.py", line 6, in background_setup
from config import settings
File "config\settings.py", line 34, in <module>
ROOT_DIR = get_root()
File "config\settings.py", line 31, in get_root
return os.path.dirname(sys.modules['__main__'].__file__)
AttributeError: 'module' object has no attribute '__file__'
... de ahí una solución basada en subprocesos
Independiente de la ubicación
Usando la misma estructura de aplicación que antes pero modificando settings.py
import os
import sys
import inspect
import platform
import threading
ROOT_DIR = None
def setup():
main_id = None
for t in threading.enumerate():
if t.name == 'MainThread':
main_id = t.ident
break
if not main_id:
raise RuntimeError("Main thread exited before execution")
current_main_frame = sys._current_frames()[main_id]
base_frame = inspect.getouterframes(current_main_frame)[-1]
if platform.system() == 'Windows':
filename = base_frame.filename
else:
filename = base_frame[0].f_code.co_filename
global ROOT_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(filename))
Desglosando esto: Primero queremos encontrar con precisión el ID del hilo del hilo principal. En Python3.4 +, la biblioteca de subprocesos tiene threading.main_thread(), sin embargo, no todo el mundo usa 3.4+, por lo que buscamos en todos los subprocesos buscando el subproceso principal, salvo su ID. Si el hilo principal ya ha salido, no aparecerá en la lista threading.enumerate(). Planteamos un RuntimeError()en este caso hasta que encuentre una mejor solución.
main_id = None
for t in threading.enumerate():
if t.name == 'MainThread':
main_id = t.ident
break
if not main_id:
raise RuntimeError("Main thread exited before execution")
A continuación, encontramos el primer marco de pila del hilo principal. Usando la función específica de cPython sys._current_frames() obtenemos un diccionario del marco de pila actual de cada hilo. Luego, utilizando inspect.getouterframes()podemos recuperar la pila completa para el hilo principal y el primer marco. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Finalmente, las diferencias entre las implementaciones de Windows y Linux inspect.getouterframes()deben ser manejadas. Usar el nombre de archivo limpiado os.path.abspath()y os.path.dirname()limpiar las cosas.
if platform.system() == 'Windows':
filename = base_frame.filename
else:
filename = base_frame[0].f_code.co_filename
global ROOT_DIR
ROOT_DIR = os.path.dirname(os.path.abspath(filename))
Hasta ahora he probado esto en Python2.7 y 3.6 en Windows, así como Python3.4 en WSL
<ROOT>/__init__.pyexiste?