在我正在使用的大型应用程序中,有几个人以不同的方式导入相同的模块 导入x 要么 从y import x 如果有人依赖全局属性,那么x的副作用是导入两次并且可能会引入非常微妙的错误
e.g。假设我有一个mypakcage包,包含三个文件mymodule.py,main.py和 init .py
mymodule.py内容
l = []
class A(object): pass
main.py内容
def add(x):
from mypackage import mymodule
mymodule.l.append(x)
print "updated list",mymodule.l
def get():
import mymodule
return mymodule.l
add(1)
print "lets check",get()
add(1)
print "lets check again",get()
打印
updated list [1]
lets check []
updated list [1, 1]
lets check again []
因为现在两个不同的模块中有两个列表,类似的A类是不同的 对我来说,它看起来很严重,因为类本身将被区别对待 例如下面的代码打印False
def create():
from mypackage import mymodule
return mymodule.A()
def check(a):
import mymodule
return isinstance(a, mymodule.A)
print check(create())
问题:
有什么方法可以避免这种情况吗?除了强制执行该模块应该以单向onyl方式导入。这不能由python导入机制处理,我在django代码和其他地方也看到过与此相关的几个bug。
答案 0 :(得分:4)
每个模块命名空间只导入一次。问题是,你以不同的方式导入它们。在第一个从全局包导入时,第二个是在进行本地非打包import
。 Python认为模块不同。第一个导入内部缓存为mypackage.mymodule
,第二个导入仅为mymodule
。
解决此问题的方法是始终使用绝对导入。也就是说,始终从顶级包开始为您的模块提供绝对导入路径:
def add(x):
from mypackage import mymodule
mymodule.l.append(x)
print "updated list",mymodule.l
def get():
from mypackage import mymodule
return mymodule.l
请记住,您的入口点(您运行的文件main.py
)也应该在之外。当您希望入口点代码在包内时,通常使用运行小脚本代替。例如:
runme.py
,在包裹之外:
from mypackage.main import main
main()
在main.py
中添加:
def main():
# your code
我发现Jp Calderone的this document是关于如何(不)构建python项目的一个很好的提示。在它之后你将不会有问题。注意bin
文件夹 - 它在包外。我将在这里重现整个文本:
Python项目的文件系统结构
<强>不要强>:
- 将目录命名为 与您的项目有关。例如, 如果您的项目名为“ Twisted ”, 为其命名顶级目录 源文件
Twisted
。当你这样做 发布时,您应该包含一个版本 数字后缀:Twisted-2.5
。- 创建目录
Twisted/bin
和 把你的可执行文件放在那里,如果你 有什么。不要给他们.py
扩展,即使它们是Python 源文件。不要输入任何代码 他们除了导入和调用一个 主要功能在其他地方定义 在你的项目中。- 如果您的项目 可以表示为单个Python 源文件,然后把它放入 目录并命名它 与您的项目有关。例如,
Twisted/twisted.py
。如果你需要 多个源文件,创建一个 改为包裹(Twisted/twisted/
, 空着Twisted/twisted/__init__.py
)和 将源文件放入其中。对于 例,Twisted/twisted/internet.py
。- 放 你的单元测试在子包中 你的包裹(注意 - 这意味着 单个Python源文件选项 以上是一个技巧 - 你总是需要 至少一个其他文件为您的单位 测试)。例如,
Twisted/twisted/test/
。当然, 把它作为一个包Twisted/twisted/test/__init__.py
。 将测试放在像Twisted/twisted/test/test_internet.py
。- 添加
Twisted/README
和Twisted/setup.py
来解释和 分别安装你的软件 如果你感觉很好。<强>不强>:
- 将您的来源放在目录中 称为
src
或lib
。这样做 没有安装就难以运行。- 放 您在Python之外的测试 包。这使得很难运行 测试已安装的版本。
- 创建一个只有一个的包
__init__.py
然后把你所有的 代码转换为__init__.py
。做一个 它是模块而不是包 简单。- 试着想出来 使Python成为可能的神奇黑客 无需导入您的模块或包 让用户添加目录 将其包含在导入路径中 (通过
PYTHONPATH
或其他方式 机制)。你不会正确的 处理所有案例,用户将得到 当你的软件对你生气 在他们的环境中不起作用。
答案 1 :(得分:3)
如果main.py是您实际运行的文件,我只能复制它。在这种情况下,您将获得sys路径上main.py的当前目录。但是你显然也设置了系统路径,以便可以导入mypackage。
Python会在那种情况下没有意识到mymodule和mypackage.mymodule是同一个模块,你会得到这个效果。这一变化说明了这一点:
def add(x):
from mypackage import mymodule
print "mypackage.mymodule path", mymodule
mymodule.l.append(x)
print "updated list",mymodule.l
def get():
import mymodule
print "mymodule path", mymodule
return mymodule.l
add(1)
print "lets check",get()
add(1)
print "lets check again",get()
$ export PYTHONPATH=.
$ python mypackage/main.py
mypackage.mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
mymodule path <module 'mymodule' from '/tmp/mypackage/mymodule.pyc'>
但是在当前目录中添加另一个主文件:
realmain.py:
from mypackage import main
,结果不同:
mypackage.mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
所以我怀疑你的主要python文件在包中。在这种情况下,解决方案是不这样做。 : - )