9

在我正在工作的一个大型应用程序中,几个人以不同的方式导入相同的模块,例如 import x 或 from y import x 的副作用是 x 被导入两次,如果有人依赖全局属性,可能会引入非常微妙的错误

例如,假设我有一个包含三个文件 mymodule.py、main.py 和init .py的包 mypakcage

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())

问题:

有没有办法避免这种情况?除了强制该模块应该以一种方式导入外。这不能通过 python 导入机制来处理,我在 django 代码和其他地方也看到了一些与此相关的错误。

4

2 回答 2

5

每个模块命名空间只导入一次。问题是,您以不同的方式导入它们。第一次是从全局包中导入,第二次是本地的、未打包的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 的这篇文档是关于如何(不)构建你的 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和 T来解释和安装您的软件。wisted/setup.py

不要

  • 将您的源代码放在名为srcor的目录中lib。这使得不安装就很难运行。
  • 把你的测试放在你的 Python 包之外。这使得很难针对已安装的版本运行测试。
  • 创建一个只有 a 的包, __init__.py然后将所有代码放入__init__.py. 只需制作一个模块而不是一个包,它更简单。
  • 尝试想出一些神奇的技巧,让 Python 能够导入您的模块或包,而无需用户将包含它的目录添加到他们的导入路径(通过PYTHONPATH或其他机制)。您不会正确处理所有情况,当您的软件在他们的环境中无法运行时,用户会生您的气。
于 2009-09-22T11:47:06.227 回答
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 文件。在这种情况下,解决方案是不这样做。:-)

于 2009-09-22T11:46:25.473 回答