使用__init__.py的Python子模块导入

42 投票
3 回答
51958 浏览
提问于 2025-04-18 10:20

我正在学习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

3 个回答

2

在Python中,有一个绝对不变的规则,就是你使用的名字必须在你所在的模块中定义或者导入。在这里,你在test.py文件里没有导入任何东西,所以就像错误信息所说的那样,do_something没有被定义。

即使你的package/__init__.py文件被执行了(不过,正如其他人所指出的,它并没有被执行),你的代码还是无法正常工作,因为如果你想在test.py文件中使用do_something,你必须在test.py里导入它。

4

首先,你得明白单独使用 import 是怎么回事:

import test.submodule.do_something

这会尝试从已经加载的 test 中的 submodule 加载 do_something

你想从 subpackage 中加载东西,所以先从这里开始:

import subpackage

好的,subpackage/__init__.py 被加载了。

现在,你想要的 do_something() 函数在一个文件(也叫“模块”)hello_world.py 中。很简单:

from subpackage.hello_world import do_something

这样就完成了!大声读出这一行,它做的正是它所说的:从 subpackage 包中的 hello_world 模块导入 do_something

test.py 中试试这个:

from subpackage.hello_world import do_something

do_something()

应该可以正常工作。

接下来,第二个问题:

__init__.pypackage/ 中不会被调用,因为你没有把 package/ 当作一个包来使用。只有当你导入 package/ 或者其中的任何东西时,__init__.py 才会被使用,比如:

from package import test

否则,它根本不会被加载。

不过,如果你想在导入子包时加载 do_something(),就把 from submodule.hello_world import do_something 放在 subpackage/__init__.py 中,然后在你的 test.py 中执行 import subpackage

37

你可能已经明白,当你导入一个模块时,解释器会创建一个新的命名空间,并在这个新的命名空间中执行模块的代码,这个命名空间既是局部的也是全局的。当代码执行完毕后,模块名(或者在任何as子句中给出的名称)会与刚刚创建的模块对象绑定,并记录在sys.modules中的__name__里。

当你导入一个像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只导入你特别想要的名称。

最终,保持清晰的理解导入是如何工作的,以及它的各种形式对导入命名空间的影响,是最好的工具。

撰写回答