我最近遇到了一个关于 Python 中模块导入的令人困惑的问题,特别是根据不同的导入样式复制类实例的方式。
我必须努力才能繁殖。这并不容易。
主要问题
根据导入风格,模块变量可以重复(实例化多次,即使没有循环导入)。在编程时很难看到这一点,而且调试起来也很乏味。
主要问题
避免此问题的最佳做法是什么?
插图
这是一个简单的项目
PROJECT ROOT FOLDER
└───app
│ main.py
│
└───websocket
a.py
b.py
ws.py
__init__.py
main.py
import sys
def log_modules():
print("\n\nModules currently in cache:")
for module_name in sys.modules:
if ("web" in module_name or "ws" in module_name) and ("windows" not in module_name and "asyncio" not in module_name):
print(f" - {module_name}: {id(sys.modules[module_name])}")
from app.websocket.a import a
log_modules()
from app.websocket.b import b
log_modules()
if __name__ == "__main__":
a()
b()
ws.py
class ConnectionManager:
def __init__(self):
print(f"New ConnectionManager object created, id: {id(self)}")
self.caller_list = []
def use(self, caller):
self.caller_list.append(caller)
print(f"ConnectionManager object used by {caller}, id: {id(self)}. Callers = {self.caller_list}")
websocket_manager = ConnectionManager()
a.py
from websocket.ws import websocket_manager # <= one import style: legitimate
def a():
websocket_manager.use("a")
b.py
from .ws import websocket_manager # <= another import style: legitimate also
def b():
websocket_manager.use("b")
它输出:
New ConnectionManager object created, id: 1553357629648
New ConnectionManager object created, id: 1553357630608
ConnectionManager object used by a, id: 1553357629648. Callers = ['a']
ConnectionManager object used by b, id: 1553357630608. Callers = ['b']
当我们期望只有一个
ConnectionManager
实例时。
我相信这两种导入都是合法的,特别是在可能出现不同风格的开发团队中(即使我们不希望:出现此问题)。
问题是:盲目应用的最佳做法应该是什么?
补充问题 模块日志显示:
Modules currently in cache:
- app.websocket: 1553355041312
- websocket: 1553357652672
- websocket.ws: 1553357652512 <= here we are after main.py from app.websocket.a import a
- app.websocket.a: 1553355040112
- app.websocket.ws: 1553357653632
- app.websocket.b: 1553357652112 <= here we are after main.py from app.websocket.b import b
我们可以看到问题:
ws
模块被导入两次:一次作为websocket.ws
,另一个作为app.websocket.ws
。感谢您的帮助。
正确做法:
__init__.py
添加到 app/
,使 app
成为一个包(而不是作为隐式命名空间包)。PROJECT ROOT FOLDER
,使用 app/main.py
将 python -m app.main
作为模块运行。 (不要运行 python app/main.py
或类似内容。)这样就不可能将
app.websocket
导入为websocket
,因为它根本无法在sys.path
上使用。 (只有 PROJECT ROOT FOLDER
会位于 sys.path
上,并且 websocket
不是它的直接后代,因此它不会像 import websocket
一样导入。)