Python中的import关键字实际上是如何工作的?

4
假设我有三个文件:
a.py
from d import d

class a:
    def type(self):
        return "a"
    def test(self):
        try:
            x = b()
        except:
            print "EXCEPT IN A"
            from b import b
            x = b()
        return x.type()

b.py

import sys

class b:
    def __init__(self):
        if "a" not in sys.modules:
            print "Importing a!"
            from a import a
        pass
    def type(self):
        return "b"
    def test(self):
        for modules in sys.modules:
            print modules
        x = a()
        return x.type()

c.py

from b import b
import sys

x = b()
print x.test()

然后运行python c.py

Python返回以下错误:

NameError: 全局名称'a'未定义

但是,a在sys.modules中存在:

copy_reg
sre_compile
locale
_sre
functools
encodings
site
__builtin__
operator
__main__
types
encodings.encodings
abc
errno
encodings.codecs
sre_constants
re
_abcoll
ntpath
_codecs
nt
_warnings
genericpath
stat
zipimport
encodings.__builtin__
warnings
UserDict
encodings.cp1252
sys
a
codecs
os.path
_functools
_locale
b
d
signal
linecache
encodings.aliases
exceptions
sre_parse
os

我可以修改b.py文件如下:

x = a()
改为
x = sys.modules["a"].a()

这样Python就能正常运行了。

由此引发了一些问题:

为什么Python说它不知道a是什么,而它在sys.modules中存在?
使用sys.modules访问类和函数定义是否是一种“正确”的方式?
导入模块的“正确”方法是什么?
例如:
from module import x
或者
import module

4个回答

3

我猜这是一个作用域的问题,如果你在构造函数中导入一个模块,那么你只能在构造函数中使用它,在导入语句之后。


2
根据Python文档,导入语句分为两步执行:(1)查找模块并在必要时初始化;(2)在导入语句所在的作用域中定义一个或多个名称在本地命名空间中。因此问题在于,虽然已经导入了模块a,但是名称a仅绑定在b.__init__方法的作用域中,而不是整个b.py的作用域。因此,在b.test方法中没有这样的名称a,因此会出现NameError错误。您可能需要阅读这篇关于导入Python模块的文章,因为它有助于解释使用import的最佳实践。

1
在您的情况下,a 在 sys.modules 中,但并非 sys.modules 中的所有内容都在 b 的作用域中。如果您想使用 re,则还需要导入它。
条件导入有时是可接受的,但这不是其中之一。首先,在此情况下 a 和 b 之间的循环依赖关系不幸,应该避免(Fowler 的重构中有许多模式可以做到这一点)。话虽如此,在此处没有必要进行条件导入。
b 应该简单地导入 a。为什么您不直接在文件顶部导入它来避免它呢?

我正在进行一些测试,忘记将原始代码改回来。最初,我导入了b和b导入a。这就是为什么我将其作为条件导入移动的原因。 - Natalie Adams

0

根据程序逻辑有条件地导入代码模块是不好的风格。在您的代码中,名称应始终意味着相同的事情。想象一下调试时会有多么令人困惑:

if (something)
  from office import desk
else
  from home import desk

... somewhere later in the code...
desk()

即使您没有作用域问题(这很可能会出现),它仍然令人困惑。

将所有导入语句放在文件顶部。其他程序员会在那里查找。

至于使用“from foo import bar”还是只使用“import foo”,权衡的是输入更多(必须键入“foo.bar()”或只键入“bar()”)与清晰和明确之间的关系。 如果您希望您的代码非常易读且无歧义,请只说“import foo”并在每个地方完全指定调用。 请记住,阅读代码比编写代码要困难得多。


2
实际上,当您拥有可选依赖项时,这可能是一个非常好的功能。它在Python标准库中被广泛使用。主要的问题是要确保您声明的任何令牌(在本例中为“desk”)在某种意义上是“等效”的。也就是说,您将使用来自“office”的desk实现或来自“home”的desk实现,但它们都是桌子,因此您可以使用其中任何一个执行相同的操作。 - Daniel Pryden
我能看到创建可选依赖项的唯一好处就是它是一种性能优化。无论用户需要什么功能,您都可以始终导入所有依赖项,但这会使用比您想要使用的更多的内存,并且可能会降低性能。这可能是为什么标准库中使用可选导入的原因。性能优化通常会破坏代码并使其不太易读,这也不例外。最清晰的方法是尽可能在文件顶部放置导入 - 这适用于我所知道的几乎所有编程语言。 - eeeeaaii

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接