Recentemente, tenho tentado fazer algo semelhante e descobri que essas respostas são inadequadas para meus casos de uso (uma biblioteca distribuída que precisa detectar a raiz do projeto). Principalmente, tenho lutado contra diferentes ambientes e plataformas, e ainda não encontrei algo perfeitamente universal.
Código local para projeto
Eu vi este exemplo mencionado e usado em alguns lugares, Django, etc.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Por mais simples que seja, só funciona quando o arquivo em que o snippet está, na verdade, faz parte do projeto. Não recuperamos o diretório do projeto, mas sim o diretório do snippet
Da mesma forma, a abordagem sys.modules falha quando chamada de fora do ponto de entrada do aplicativo, especificamente, observei que um thread filho não pode determinar isso sem relação com o módulo ' principal '. Coloquei explicitamente a importação dentro de uma função para demonstrar uma importação de um thread filho, movê-la para o nível superior de app.py resolveria isso.
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
A execução deste programa produz um erro 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__'
... portanto, uma solução baseada em threading
Independente de localização
Usando a mesma estrutura de aplicativo de antes, mas 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))
Resolvendo isso: Primeiro, queremos encontrar com precisão o ID do thread principal. No Python3.4 +, a biblioteca de threading tem threading.main_thread()
, entretanto, todo mundo não usa 3.4+, então pesquisamos todos os threads procurando pelo thread principal, exceto seu ID. Se o tópico principal já foi encerrado, ele não será listado no threading.enumerate()
. Levantamos um RuntimeError()
neste caso até encontrar uma solução melhor.
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")
Em seguida, encontramos o primeiro frame de pilha do thread principal. Usando a função específica cPython sys._current_frames()
, obtemos um dicionário de cada frame de pilha atual do thread. Em seguida, utilizando inspect.getouterframes()
, podemos recuperar a pilha inteira para o thread principal e o primeiro quadro. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Finalmente, as diferenças entre as implementações do Windows e do Linux inspect.getouterframes()
precisam ser tratadas. Use o nome do arquivo limpo os.path.abspath()
e os.path.dirname()
limpe as coisas.
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))
Até agora, testei isso no Python 2.7 e 3.6 no Windows, bem como no Python3.4 no WSL
<ROOT>/__init__.py
existe?