Python子模块导入使用__init__.py

42

我正在学习Python,但我无法理解__init__.py文件中的导入是如何工作的。

我从Python教程中了解到,__init__.py文件用于初始化一个包,并且可以在此处导入子包。

不过我似乎做错了什么。您能否为我(以及将来学习Python的人)解释一下我做错了什么?

这是我尝试做的简化示例:

这是我的文件结构:

package
    __init__.py
    test.py
    subpackage
        __init__.py
        hello_world.py
<代码>hello_world.py的内容如下:
def do_something():
    print "Hello, world!"
subpackage/__init__.py 文件是空的。 package/__init__.py 文件包含如下内容:
import test.submodule.do_something

最后,test.py 包含如下内容:
do_something()

这是我在使用OSX终端和Python 3运行hello_world.py的尝试:

python test.py

Python会抛出以下错误:

NameError: name 'do_something' is not defined

我猜测你正在运行的 test.py 是在 package/test.py 目录下?如果是这样,我认为它不需要在一个包中,因此 package/__init__.py 似乎是完全无关紧要的。 - holdenweb
3个回答

35

当你导入一个模块时,解释器会创建一个新的命名空间,并使用这个新的命名空间作为本地和全局命名空间来执行该模块的代码。当代码执行完成后,模块名称(或在任何as子句中给出的名称)将绑定到刚刚在导入命名空间内创建的模块对象上,并记录在其__name__中,在sys.modules中。

当导入限定名称(例如package.subpackage.module)时,第一个名称(package)被导入到本地命名空间中,然后subpackage被导入到package的命名空间中,最后module被导入到package.subpackage的命名空间中。使用from ... import ... as ...的导入执行相同的操作序列,但导入的对象直接绑定到导入模块的命名空间中的名称上。包名称未绑定在本地命名空间中并不意味着它未被导入(如检查sys.modules所示)。

__init__.py在包中的作用与模块的.py文件基本相同。一个有一个结构, 被写成一个目录,该目录还可以包含模块(常规的.py文件)和子目录(也包含一个__init__.py文件)用于任何子包。当导入包时,将创建一个新的命名空间,并使用该命名空间作为本地和全局命名空间来执行包的__init__.py。因此,为了解决您的问题,我们可以通过省略顶级包来简化您的文件存储,当以程序方式运行test.py时,解释器永远不会考虑它。那么看起来是这样的:

test.py
subpackage/
    __init__.py
    hello_world.py

现在,subpackage不再是子包,因为我们已将其所属的包删除为不相关。关注为什么do_something名称未定义可能有所帮助。test.py中没有任何导入,因此不清楚您如何期望do_something获得含义。您可以通过使用空的subpackage/__init__.py来使其正常工作,然后您可以编写test.py

from subpackage.hello_world import do_something
do_something()

或者您可以使用一个subpackage/__init__.py文件来读取。

from hello_world import do_something

当导入包时,它会在subpackage命名空间内建立do_something函数。然后使用一个test.py文件从该包中导入该函数,如下所示:

from subpackage import do_something
do_something()

最后一个选择与相同的__init__.py一起使用一个test.py,只需导入(子)包,然后使用相对命名来访问所需的函数:

import subpackage
subpackage.do_something()

为了在您的本地命名空间中访问它。

通过空的__init__.py文件,也可以通过读取test.py文件来实现这一点。

import subpackage.hello_world
subpackage.hello_world.do_something()

甚至连

from subpackage.hello_world import do_something
do_something()
一个空的__init__.py文件意味着顶层包命名空间只包含程序导入的任何子包的名称,这使您能够仅导入所需的子包。这为您控制了顶层包的命名空间。
虽然在__init__.py中定义类和函数是完全可能的,但更常见的方法是从子模块导入内容到该命名空间中,以便导入者可以仅通过单级属性引用导入顶层包以访问其内容,甚至使用from仅导入您明确想要的名称。
最终,保持清晰的import工作原理和其各种形式对导入命名空间的影响的理解是让您保持正确的最佳工具。

2
啊,我现在明白了。我的错误在于认为“import”与PHP或C中的“include”类似,并且__init__.py是在包被使用之前总是会运行的代码。感谢您的完美答案。 - Benjamin
2
很高兴能够帮助你,我以教授这些内容为生,但是当我不忙的时候,重新加入社区并帮助人们是非常棒的。 - holdenweb
1
我怀疑将一个模块作为子包导入或从子包中导入可能会导致重复导入子包:即使另一个导入语句已经导入了子包,import subpackage.hello_world 也会重新导入子包。我猜谷歌的Python编码指南简要提到了这一点,https://google-styleguide.googlecode.com/svn/trunk/pyguide.html?showone=Packages#Packages - eel ghEEz
2
这篇笔记指出了双重导入的危险性,https://utcc.utoronto.ca/~cks/space/blog/python/RelativeImportProblem - eel ghEEz
你为什么觉得不能把它放在__init__.py中?既然你问了,我已经编辑了答案来明确解决这个问题 - 希望能帮到你。 - holdenweb
显示剩余2条评论

4

首先,您需要了解如何单独使用import

import test.submodule.do_something

尝试从自身加载的 test 子模块中加载do_something

您需要从 subpackage 加载内容,因此请从该子模块开始:

import subpackage

好的,subpackage/__init__.py已加载。

现在,您想要的是位于文件(一个“模块”)hello_world.py中的do_something()函数。很容易:

from subpackage.hello_world import do_something

完成了!只需大声朗读这一行,它恰好做到了这样:从名为subpackage的模块hello_world中导入do_something

test.py中尝试吧!

from subpackage.hello_world import do_something

do_something()

应该可以正常工作。

现在,第二个问题:

__init__.py不会在 package/ 中被调用,因为您没有将 package/ 作为一个包使用。只有当您导入 package/ 或其中的任何内容时,__init__.py 才会被使用,例如:

from package import test

否则,它根本不会加载。
但是,如果你想在子包导入时加载do_something(),请在subpackage/__init__.py中放置from submodule.hello_word import do_something,然后在你的test.py中对subpackage进行import操作。

非常感谢!我现在明白了。 - Benjamin
我不相信你可以导入一个单独的函数,这就是 import test.submodule.do_something 尝试做的事情。如果你想让引用 test.submodule.do_something 有效,你只能使用 import test.submodule。如果你 import os.path,你会发现 os 模块存在于导入命名空间中 - 否则 os.path 又在哪里呢? - holdenweb

2
在Python中,一个绝对的硬性规定是你必须在使用名称的模块内定义或导入它。在这里,你从未在test.py内导入任何东西 - 因此正如错误所说,do_something未定义。
即使执行了package/__init__.py文件(正如其他人指出的那样),你的代码仍然无法工作,因为如果想在该文件中引用它,则必须在test.py内部进行do_something的导入。

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