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

发布于 2024-08-05 08:04:32 字数 1073 浏览 3 评论 0原文

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

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

mymodule.py 内容

l = []
class A(object): pass

ma​​in.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 代码和其他地方也看到了几个与此相关的错误。

In a big application I am working, several people import same modules differently e.g.
import x
or
from y import x
the side effects of that is x is imported twice and may introduce very subtle bugs, if someone is relying on global attributes

e.g. suppose I have a package mypakcage with three file mymodule.py, main.py and init.py

mymodule.py contents

l = []
class A(object): pass

main.py contents

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

it prints

updated list [1]
lets check []
updated list [1, 1]
lets check again []

because now there are two lists in two different modules, similarly class A is different
To me it looks serious enough because classes itself will be treated differently
e.g. below code prints False

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

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

print check(create())

Question:

Is there any way to avoid this? except enforcing that module should be imported one way onyl. Can't this be handled by python import mechanism, I have seen several bugs related to this in django code and elsewhere too.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

污味仙女 2024-08-12 08:04:32

每个模块命名空间仅导入一次。问题是,您以不同的方式导入它们。第一个是从全局包导入,第二个是本地非打包导入。 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
    源文件。不要输入任何代码
    除了导入和调用之外
    main 函数在其他地方定义
    在您的项目中。
  • 如果您的项目
    可表示为单个 Python
    源文件,然后将其放入
    目录并为其命名
    与您的项目相关。例如,
    Twisted/twisted.py。如果您需要
    多个源文件,创建一个
    改为包(Twisted/twisted/
    与一个空的
    Twisted/twisted/__init__.py) 和
    将您的源文件放入其中。为了
    例子,
    Twisted/twisted/internet.py

  • 您的单元测试在一个子包中
    您的包裹(注意 - 这意味着
    单个 Python 源文件选项
    上面是一个技巧 - 你总是需要
    您单位的至少一份其他文件
    测试)。例如,
    扭曲/扭曲/测试/。当然,
    使其成为一个包
    Twisted/twisted/test/__init__.py
    将测试放在类似的文件中
    Twisted/twisted/test/test_internet.py
  • 添加 Twisted/README 和 Twisted/setup.py 进行解释和
    分别安装您的软件,
    如果你感觉不错的话。

不要

  • 将源代码放入目录中
    称为srclib。这使得
    不安装就很难运行。

  • 在 Python 之外进行测试
    包裹。这使得运行变得困难
    针对已安装的版本进行测试。
  • 创建一个只有一个包
    __init__.py 然后把你所有的
    代码写入__init__.py。只需制作一个
    模块而不是包,它是
    更简单。
  • 尝试想出
    神奇的黑客让Python能够
    导入你的模块或包而不
    让用户添加目录
    将其包含到导入路径中
    (通过 PYTHONPATH 或其他一些
    机制)。你不会正确地
    处理所有情况,用户将得到
    当你的软件对你生气时
    在他们的环境中不起作用。

Each module namespace is imported only once. Issue is, you're importing them differently. On the first you're importing from the global package, and on the second you're doing a local, non-packaged import. Python sees modules as different. The first import is internally cached as mypackage.mymodule and the second one as mymodule only.

A way to solve this is to always use absolute imports. That is, always give your module absolute import paths from the top-level package onwards:

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

Remember that your entry point (the file you run, main.py) also should be outside the package. When you want the entry point code to be inside the package, usually you use a run a small script instead. Example:

runme.py, outside the package:

from mypackage.main import main
main()

And in main.py you add:

def main():
    # your code

I find this document by Jp Calderone to be a great tip on how to (not) structure your python project. Following it you won't have issues. Pay attention to the bin folder - it is outside the package. I'll reproduce the entire text here:

Filesystem structure of a Python project

Do:

  • name the directory something
    related to your project. For example,
    if your project is named "Twisted",
    name the top-level directory for its
    source files Twisted. When you do
    releases, you should include a version
    number suffix: Twisted-2.5.
  • create a directory Twisted/bin and
    put your executables there, if you
    have any. Don't give them a .py
    extension, even if they are Python
    source files. Don't put any code in
    them except an import of and call to a
    main function defined somewhere else
    in your projects.
  • If your project
    is expressable as a single Python
    source file, then put it into the
    directory and name it something
    related to your project. For example,
    Twisted/twisted.py. If you need
    multiple source files, create a
    package instead (Twisted/twisted/,
    with an empty
    Twisted/twisted/__init__.py) and
    place your source files in it. For
    example,
    Twisted/twisted/internet.py.
  • put
    your unit tests in a sub-package of
    your package (note - this means that
    the single Python source file option
    above was a trick - you always need at
    least one other file for your unit
    tests). For example,
    Twisted/twisted/test/. Of course,
    make it a package with
    Twisted/twisted/test/__init__.py.
    Place tests in files like
    Twisted/twisted/test/test_internet.py.
  • add Twisted/README and Twisted/setup.py to explain and
    install your software, respectively,
    if you're feeling nice.

Don't:

  • put your source in a directory
    called src or lib. This makes it
    hard to run without installing.
  • put
    your tests outside of your Python
    package. This makes it hard to run the
    tests against an installed version.
  • create a package that only has a
    __init__.py and then put all your
    code into __init__.py. Just make a
    module instead of a package, it's
    simpler.
  • try to come up with
    magical hacks to make Python able to
    import your module or package without
    having the user add the directory
    containing it to their import path
    (either via PYTHONPATH or some other
    mechanism). You will not correctly
    handle all cases and users will get
    angry at you when your software
    doesn't work in their environment.
画骨成沙 2024-08-12 08:04:32

仅当 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 文件在包中。在这种情况下,解决方案就是不这样做。 :-)

I can only replicate this if main.py is the file you are actually running. In that case you will get the current directory of main.py on the sys path. But you apparently also have a system path set so that mypackage can be imported.

Python will in that situation not realize that mymodule and mypackage.mymodule is the same module, and you get this effect. This change illustrates this:

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'>

But add another mainfile, in the currect directory:

realmain.py:
from mypackage import main

and the result is different:

mypackage.mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>
mymodule path <module 'mypackage.mymodule' from '/tmp/mypackage/mymodule.pyc'>

So I suspect that you have your main python file within the package. And in that case the solution is to not do that. :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文