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

时间:2009-09-22 10:17:31

标签: python python-import

在我正在使用的大型应用程序中,有几个人以不同的方式导入相同的模块 导入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。

2 个答案:

答案 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和T wisted/setup.py来解释和   分别安装你的软件   如果你感觉很好。
  •   
     

<强>不

     
      
  • 将您的来源放在目录中   称为srclib。这样做   没有安装就难以运行。
  •   
  • 放   您在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文件在包中。在这种情况下,解决方案是不这样做。 : - )