私は最近同様のことをしようと試みており、これらの回答は私のユースケース(プロジェクトのルートを検出する必要がある分散ライブラリ)には不十分であることがわかりました。主に私はさまざまな環境やプラットフォームと戦っていますが、それでも完全に普遍的なものは見つかりませんでした。
プロジェクトにローカルなコード
この例がDjangoなどのいくつかの場所で言及され、使用されているのを見てきました。
import os
print(os.path.dirname(os.path.abspath(__file__)))
これは単純ですが、スニペットが含まれているファイルが実際にプロジェクトの一部である場合にのみ機能します。プロジェクトディレクトリを取得するのではなく、スニペットのディレクトリを取得します
同様に、sys.modulesアプローチは、アプリケーションのエントリポイントの外側から呼び出されると機能しなくなります。具体的には、子スレッドがこれを ' main 'モジュールに関係なく判断できないことを確認しました。子スレッドからのインポートを示すために、関数内に明示的にインポートを配置しました。それを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))
これを分解する:まず、メインスレッドのスレッドIDを正確に見つけたいと思います。threading.main_thread()ただし、Python3.4以降ではスレッドライブラリに3.4以降が使用されていないため、メインスレッドを探してすべてのスレッドを検索し、IDを保存します。メインスレッドがすでに終了している場合、そのスレッドはに表示されませんthreading.enumerate()。RuntimeError()この場合は、より良い解決策が見つかるまでaをレイズします。
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]最後に、WindowsとLinuxの実装の違いをinspect.getouterframes()処理する必要があります。クリーンアップファイル名を使用して、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))
これまでのところ、WindowsのPython2.7および3.6、WSLのPython3.4でこれをテストしました
<ROOT>/__init__.py存在?