Нещодавно я намагався зробити щось подібне, і я виявив ці відповіді недостатніми для моїх випадків використання (розподілена бібліотека, яка потребує виявлення кореня проекту). В основному я бився в різних середовищах і платформах, і досі не знайшов щось ідеально універсальне.
Код локальний для проекту
Я бачив, як цей приклад згадувався та використовувався в кількох місцях, Джанго тощо.
import os
print(os.path.dirname(os.path.abspath(__file__)))
Це просто, але працює лише тоді, коли файл, у якому знаходиться фрагмент, є фактично частиною проекту. Ми не отримуємо каталог проектів, а натомість каталог фрагмента
Аналогічно, підхід sys.modules руйнується, коли викликається з-за точки входу програми, зокрема, я помітив, що дочірня нитка не може визначити це без відношення до головного модуля. Я явно поставив імпорт всередині функції, щоб продемонструвати імпорт із дочірньої нитки, переміщення його на верхній рівень app.py дозволило б це виправити.
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
Запуск цієї програми створює помилку атрибута:
>>> 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__'
... отже, рішення на основі різьби
Місцезнаходження незалежне
Використовуючи таку ж структуру програми, що і раніше, але змінюючи 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))
Розбігаючи це: Спочатку ми хочемо точно знайти ідентифікатор потоку основного потоку. Однак у Python3.4 + у бібліотеці для нарізки файлів threading.main_thread()кожен не використовує 3.4+, тому ми шукаємо всі потоки, шукаючи основний потік, окрім його ID. Якщо основний потік вже вийшов, він не буде вказаний у threading.enumerate(). У RuntimeError()цьому випадку ми піднімаємо позицію, поки я не знайду кращого рішення.
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")
Далі ми знаходимо найперший кадр стека основної нитки. Використовуючи специфічну функцію cPython, sys._current_frames() ми отримуємо словник поточного кадру стека кожного потоку. Тоді за допомогою inspect.getouterframes()ми можемо отримати весь стек для основної нитки та самого першого кадру. current_main_frame = sys._current_frames () [main_id] base_frame = inspect.getouterframes (current_main_frame) [- 1] Нарешті, inspect.getouterframes()необхідно вирішити відмінності між реалізаціями Windows та Linux . Використовуючи очищене ім’я файлу os.path.abspath()та os.path.dirname()очищайте речі.
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))
Поки що я тестував це на Python2.7 та 3.6 у Windows, а також на Python3.4 на WSL
<ROOT>/__init__.pyіснує?