我最近一直在尝试做类似的事情,但我发现这些答案不足以满足我的用例(需要检测项目根目录的分布式库)。主要是我一直在与不同的环境和平台作斗争,但仍然没有找到完全通用的东西。
项目本地代码
我在一些地方看到过这个例子,Django 等中提到和使用过。
import os
print(os.path.dirname(os.path.abspath(__file__)))
这很简单,它仅在 sn-p 所在的文件实际上是项目的一部分时才有效。 我们不检索项目目录,而是检索sn-p的目录
同样,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。在 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))
到目前为止,我已经在 Windows 上的 Python2.7 和 3.6 以及 WSL 上的 Python3.4 上对此进行了测试