如果从不同的路径导入,则重新导入模块

Anu*_*yal 8 python python-import

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

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

mymodule.py内容

l = []
class A(object): pass
Run Code Online (Sandbox Code Playgroud)

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()
Run Code Online (Sandbox Code Playgroud)

它打印

updated list [1]
lets check []
updated list [1, 1]
lets check again []
Run Code Online (Sandbox Code Playgroud)

因为现在在两个不同的模块中有两个列表,类似的A类是不同的对我来说它看起来很严重因为类本身将被区别对待,例如下面的代码打印False

def create():
    from mypackage import mymodule
    return mymodule.A()

def check(a):
    import mymodule
    return isinstance(a, mymodule.A)

print check(create())
Run Code Online (Sandbox Code Playgroud)

题:

有什么方法可以避免这种情况吗?除了强制执行该模块应该以单向onyl方式导入.这不能由python导入机制处理,我在django代码和其他地方也看到了与此相关的几个错误.

nos*_*klo 5

每个模块命名空间只导入一次。问题是,您以不同的方式导入它们。第一个是从全局包导入,第二个是本地的非打包import. Python 将模块视为不同的。第一个导入在内部缓存为mypackage.mymodule,第二个为mymoduleonly。

解决此问题的一种方法是始终使用绝对导入。也就是说,始终为您的模块提供从顶级包开始的绝对导入路径:

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
Run Code Online (Sandbox Code Playgroud)

请记住,您的入口点(您运行的文件main.py)也应该在包之外。当您希望入口点代码位于包内时,通常您使用运行小脚本来代替。例子:

runme.py,外包装:

from mypackage.main import main
main()
Run Code Online (Sandbox Code Playgroud)

并在main.py您添加:

def main():
    # your code
Run Code Online (Sandbox Code Playgroud)

我发现Jp Calderone 的这篇文档是关于如何(不)构建 Python 项目的一个很好的提示。跟着它,你不会有问题。注意bin文件夹 - 它在包外。我将在此处复制整个文本:

Python 项目的文件系统结构

  • 将目录命名为与您的项目相关的内容。例如,如果您的项目名为“ Twisted ”,则为其源文件命名顶级目录Twisted。当您发布版本时,您应该包含一个版本号后缀:Twisted-2.5.
  • 创建一个目录Twisted/bin并将您的可执行文件放在那里,如果有的话。不要给它们.py 扩展名,即使它们是 Python 源文件。除了导入和调用在项目中其他地方定义的 main 函数之外,不要在其中放置任何代码。
  • 如果您的项目可表示为单个 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来解释和安装您的软件。

不要

  • 将您的源代码放在名为srcor的目录中lib。这使得不安装就很难运行。
  • 将您的测试放在 Python 包之外。这使得很难针对已安装的版本运行测试。
  • 创建一个只有一个的包, __init__.py然后将所有代码放入__init__.py. 只需制作一个模块而不是一个包,它更简单。
  • 尝试想出一些神奇的技巧,使 Python 能够导入您的模块或包,而无需用户将包含它的目录添加到他们的导入路径中(通过PYTHONPATH或其他某种机制)。您将无法正确处理所有情况,并且当您的软件在他们的环境中不起作用时,用户会生您的气。


Len*_*bro 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'>
Run Code Online (Sandbox Code Playgroud)

但在当前目录中添加另一个主文件:

realmain.py:
from mypackage import main
Run Code Online (Sandbox Code Playgroud)

结果不同:

mypackage.mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
Run Code Online (Sandbox Code Playgroud)

所以我怀疑你的主要 python 文件在包中。在这种情况下,解决方案就是不这样做。:-)