在此之前先确认一个概念是否弄清
假设你有以下结构:
testpkg/
__init__.py
fool.py
maybe.py
内容如下:
fool.py
# testpkg/fool.py
class Fool:
pass
maybe.py
# testpkg/maybe.py
class Maybe:
pass
__init__.py
(先什么也不写,空文件)
__init__.py
在外部写测试代码:
import testpkg.fool
import testpkg.maybe
print(hasattr(testpkg, 'Fool')) # False
print(hasattr(testpkg, 'Maybe')) # False
print(hasattr(testpkg.fool, 'Fool')) # True
print(hasattr(testpkg.maybe, 'Maybe')) # True
解释:
testpkg.fool
模块的命名空间里有Fool
(因为在fool.py里定义了)testpkg.maybe
模块的命名空间里有Maybe
testpkg
(这个包)的命名空间里没有Fool
和Maybe
,因为没把它们导入到包的顶层。模块名.__dict__
看到它的命名空间。例如:
import testpkg.fool
import testpkg.maybe
print(testpkg.fool.__dict__.keys())
print(testpkg.maybe.__dict__.keys())
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 'Fool'])
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 'Maybe'])
如果你修改testpkg/__init__.py
为:
from testpkg.fool import Fool
from testpkg.maybe import Maybe
__all__ = ["Fool", "Maybe"]
import testpkg
的时候,实际上会执行 testpkg/__init__.py
里的代码。from testpkg.fool import Fool
这行代码的作用是:testpkg.fool
这个模块里的 Fool
,导入到当前命名空间(也就是 testpkg
的命名空间)下。from testpkg.maybe import Maybe
把 Maybe
导入到 testpkg
命名空间下。现在再测试:
import testpkg
print(hasattr(testpkg, 'Fool')) # True
print(hasattr(testpkg, 'Maybe')) # True
print(testpkg.__dict__.keys())
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'fool', 'Fool', 'maybe', 'Maybe', '__all__'])
此时Foo
和Bar
被“绑定”到了testpkg
这个包的命名空间下。
__all__
讲解__all__
是一个导出控制列表,是一个字符串列表。
当你这样用的时候:
from testpkg import *
Python 会只导入 __all__
里列出的名字。
也就是说:
__init__.py
里有 __all__ = ['Fool', 'Maybe']
,from testpkg import *
只会导入 Fool
和 Maybe
,__init__.py
里定义了,也不会随 *
导出。注意
__all__
只影响 from ... import *
这种用法。import testpkg
不受影响。非常好,你对模块命名空间、包、以及 __init__.py
的作用已经理解得很清楚了!下面我会在你的基础上,进一步系统性讲解 __init__.py
文件,帮助你更深入理解它的地位与用法。
__init__.py
文件__init__.py
的本质__init__.py
是 包的初始化脚本。__init__.py
,Python 才会把这个目录当做一个包(Python 3.3+ 以后支持“隐式命名空间包”,但强烈建议有 __init__.py
,便于兼容和控制行为)。import testpkg
时,Python 实际执行 testpkg/__init__.py
,并把里面的内容放进 testpkg
这个模块(包)对象的命名空间。__init__.py
的作用__init__.py
,Python 2 不认这个目录是包,会报错。__init__.py
能更清楚、兼容、可控。__init__.py
里写包初始化逻辑,比如设置全局变量、初始化状态、打印调试信息等。__init__.py
里导入(或重新命名)子模块、类、函数,让用户用更简单的方式访问包内容。# testpkg/__init__.py
from .fool import Fool
from .maybe import Maybe
这样用户可以 from testpkg import Fool
,不用知道子模块结构。from testpkg import *
导出的内容__all__
列表,决定哪些名字会被 *
导出。__all__ = ['Fool', 'Maybe']
__init__.py
里导入子包、子模块,甚至重命名,隐藏实现细节。from . import fool as _fool
# 空文件
# testpkg/__init__.py
from .fool import Fool
from .maybe import Maybe
__all__ = ['Fool', 'Maybe']
testpkg.Fool
、testpkg.Maybe
就变成包的“顶层接口”。# testpkg/__init__.py
print("testpkg包被导入了!")
_config = {"debug": True}
# testpkg/__init__.py
from . import fool
from . import maybe
__all__ = ['fool', 'maybe']
from testpkg import fool
就可以直接用 testpkg.fool.Fool
。相对导入和绝对导入
from .fool import Fool
:点号代表“当前包”,推荐包内部用相对导入。from testpkg.fool import Fool
:绝对导入,避免循环依赖出错。循环引用问题
ImportError
。__init__.py
并不是必须的,但建议始终写上
包结构的“封装”思路
__init__.py
),隐藏实现细节。__init__.py
__init__.py
的命名空间__init__.py
里加