包导入格式
导入模块时除了使用模块名进行导入, 还可以使用目录名进行导入. 例如, 在 sys.path 路径下, 有一个 dir1/dir2/mod.py 模块, 那么在任意位置处都可以使用下面这种方式导入这个模块.
- import dir1.dir2.mod
- from dir1.dir2.mod import XXX
一个实际一点的示例, 设置 PYTHONPATH 环境变量为 d:\pypath, 然后在此目录下创建以上目录和 mod.py 文件:
- set PYTHONPATH="D:\pypath"
- mkdir d:\pypath\dir1\dir2
- echo print("mod.py")>d:\pypath\dir1\dir2\mod.py
- echo x=3>>d:\pypath\dir1\dir2\mod.py
- # 进入交互式 python
- >>> import dir1.dir2.mod
mod.py
- >>> dir1.dir2.mod.x
- 3
注 1: 在 python3.3 版本及更高版本是可以导入成功的, 但是在 python3.3 之前的版本将失败, 因为缺少__init__.py 文件, 稍后会解释该文件
注 2: 顶级目录 dir1 必须位于 sys.path 列出的路径搜索列表下
如果输出 dir1 和 dir2, 将会看到它们的是模块对象, 且是名称空间.
>>> import dir1.dir2.mod
mod.py
- >>> dir1
- <module 'dir1' (namespace)>
- >>> dir1.dir2
- <module 'dir1.dir2' (namespace)>
- >>> dir1.dir2.mod
- <module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>
这种模块 + 名称空间的形式就是包(严格地说是包的一种形式), 也就是说 dir1 是包, dir2 也是包, 这种方式是包的导入形式. 包主要用来组织它里面的模块.
从上面的结果也可以看出, 包也是模块, 所以能使用模块的地方就能使用包. 例如下面的代码, 可以像导入模块一样直接导入包 dir2, 包和模块的区别在于它们的组织形式不一样, 模块可能位于包内, 仅此而已.
- import dir1.dir2
- from dir1 import dir2
另外, 导入 dir1.dir2.mod 时, 它声明的模块变量名为 dir1, 而不是 dir1.dir2.mod, 但是导入的对象却包含了 3 个模块: dir1,dir1.dir2 以及 dir1.dir2.mod. 如下:
- >>> dir()
- ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'dir1']
- >>> for key in sys.modules:
- ... if key.startswith("dir1"):
- ... print(key,":",sys.modules[key])
- ...
- dir1 : <module 'dir1' (namespace)>
- dir1.dir2 : <module 'dir1.dir2' (namespace)>
- dir1.dir2.mod : <module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>
__init__.py 文件
上面的 dir1 和 dir1.dir2 目前是空包, 或者说是空模块(再一次强调, 包就是模块). 但并不意味着它们对应的模块对象是空的, 因为模块是对象, 只要是对象就会有属性. 例如, dir1 包有如下属性:
- >>> dir(dir1)
- ['__doc__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'dir2']
之所以称为空包, 是因为它们现在仅提供了包的组织功能, 而且它们是目录, 而不像 py 文件一样, 是实实在在的可以编写模块代码的地方. 换句话说, 包现在是目录文件, 而不是真正的模块文件.
为了让包 "真正的" 成为模块, 需要在每个包所代表的目录下加入一个__init__.py 文件, 它表示让这个目录格式的模块 (也就是包) 像 py 文件一样可以写模块代码, 只不过这些模块代码是写入__init__.py 中的. 当然, 模块文件中允许没有任何内容, 所以__init__.py 文件也可以是空文件, 它仅表示让包成为真正的模块文件.
每次导入包的时候, 如果有__init__.py 文件, 将会自动执行这个文件中的代码, 就像模块文件一样, 事实上它就是让目录代表的包变成模块的, 甚至可以说它就是包所对应的模块文件(见下面示例), 所以也可以认为__init__.py 是包的初始化文件. 在 python3.3 之前, 这个文件必须存在, 否则就会报错, 因为它不认为目录是有效的模块.
现在, 在 dir1 和 dir2 下分别创建空文件__init__.py:
- type nul>d:\pypath\dir1\__init__.py
- type nul>d:\pypath\dir1\dir2\__init__.py
现在目录的层次格式如下:
λ tree /f d:\pypath
D:\PYPATH
└─dir1
│ __init__.py
└─dir2
- mod.py
- __init__.py
再去执行导入操作, 并输出包 dir1 和 dir2.
>>> import dir1.dir2.mod
mod.py
- >>> dir1
- <module 'dir1' from 'd:\\pypath\\dir1\\__init__.py'>
- >>> dir1.dir2
- <module 'dir1.dir2' from 'd:\\pypath\\dir1\\dir2\\__init__.py'>
- >>> dir1.dir2.mod
- <module 'dir1.dir2.mod' from 'd:\\pypath\\dir1\\dir2\\mod.py'>
从输出结果中不难看出, 包 dir1 和 dir1.dir2 是模块, 且它们的模块文件是各自目录下的__init__.py.
实际上, 包分为两种: 名称空间模块, 普通模块. 名称空间包是没有__init__.py 文件的, 普通包是有__init__.py 文件的. 无论是哪种, 它都是模块.
__init__.py 写什么内容
既然包是模块, 而__init__.py 文件是包的模块文件, 这个文件中应该写入什么代码? 答案是可以写入任何代码, 我们只需把它当作一个模块对待就可以. 不过, 包既然是用来组织模块的, 真正的功能性属性应该尽量写入到它所组织的模块文件中(也就是示例中的 mod.py).
但有一项__all__是应该在__init__.py 文件中定义的, 它是一个列表, 用来控制 from package import * 使用 * 导入哪些模块文件. 这里的 * 并非像想象中那样会导入包中的所有模块文件, 而是只导出__all__列表中指定的模块文件.
例如, 在 dir1.dir2 包下有 mod1.py,mod2.py,mod3.py 和 mod4.py, 如果在 dir2/__init__.py 文件中写入:
__all__ = ["mod1", "mod2", "mod3"]
则执行:
from dir1.dir2 import *
不会导入 mod4, 而是只导入 mod1-mod3.
如果不设置__all__, 则 from dir1.dir2 import * 不会导入该包下的任何模块, 但会导入 dir1 和 dir1.dir2.
__path__属性
严格地说, 只有当某个模块设置了__path__属性时, 才算是包, 否则只算是模块. 这是包的绝对严格定义.
__path__属性是一个路径列表(可迭代对象即可, 但通常用列表), 和 sys.path 类似, 该列表中定义了该包的初始化模块文件__init__.py 的路径.
只要导入的是一个包(无论是名称空间包还是普通包), 首先就会设置该属性, 默认导入目录时该属性会初始化当前目录, 然后去该属性列出的路径下搜索__init__.py 文件对包进行初始化. 默认情况下由于__init__.py 文件后执行, 在此文件中可以继续定义或修改__path__属性, 使得 python 会去找其它路径下的__init__.py 对模块进行初始化.
以下是默认初始化后的__path__值:
- >>> import dir1.dir2
- >>> dir1.dir2.__path__
- ['d:\\pypath\\dir1\\dir2']
- >>> import dir1.dir3
- >>> dir1.dir3
- <module 'dir1.dir3' (namespace)>
- >>> dir1.dir3.__path__
- _NamespacePath(['d:\\pypath\\dir1\\dir3'])
一般来说, 几乎不会设置__path__属性.
导入示例
import 和 from 导入时有多种语法可用, 这两个语句的导入方式和导入普通模块的方式是一样的: import 导入时需要使用前缀名称去引用, from 导入时是赋值到当前程序的同名全局变量中. 如果不了解, 请看前一篇文章: python 模块导入细节.
假设现在有如下目录结构, 且 d:\pypath 位于 sys.path 列表中:
- $ tree -f d:\pypath
- d:\pypath
└── dir1
├── __init__.py
└── dir2
├── __init__.py
└── mod.py
只导入包:
- import dir1 # 导入包 dir1
- import dir1.dir2 # 导入包 dir1.dir2
- from dir1 import dir2 # 导入包 dir1.dir2
导入某个模块:
- import dir1.dir2.mod
- from dir1.dir2 import mod
如果 dir2/__init__.py 中设置了__all__, 则下面的导入语句会导入已设置的模块:
from dir1.dir2 import *
注意, 只支持上面这种 from...import * 语法, 不支持 import *.
导入模块中的属性, 比如变量 x:
from dir1.dir2.mod import x
相对路径导入
注: 如果允许, 不要使用相对路径导入, 很容易出错, 特别是对新手而言. 使用绝对路径导入, 并将包放在 sys.path 的某个路径下就可以.
假设现在有如下目录结构:
- $ tree -f d:\pypath
- d:\pypath
└── dir1
├── __init__.py
├── dir4
│ ├── __init__.py
│ ├── c2.py
│ └── c1.py
├── dir3
│ ├── __init__.py
│ ├── b3.py
│ ├── b2.py
│ └── b1.py
└── dir2
├── __init__.py
├── a4.py
├── a3.py
├── a2.py
└── a1.py
在 dir1.dir2.a1 模块文件中想要导入 dir1.dir3.b2 模块, 可以在 a1.py 中使用下面两种方式导入:
- import dir1.dir3.b2
- from dir1.dir2. import b2
上面的导入方式是使用绝对路径进行导入的, 只要使用绝对路径, 都是从 sys.path 开始搜索的. 例如, 上面是从 sys.path 下搜索 dir1, 再依次搜索 dir1.dir3.b2.
python 还支持包的相对路径的导入, 只要使用. 或.. 即可, 就像操作系统上的相对路径一样. 使用相对路径导入时不会搜索 sys.path.
相对路径导入方式只有 from...import 支持, import 语句不支持, 且只有使用. 或.. 的才算是相对路径, 否则就是绝对路径, 就会从 sys.path 下搜索.
例如, 在 a1.py 中导入 dir1.dir3.b2:
from ..dir3 import b2
注意, 必须不能直接 python a1.py 执行这个文件, 这样会报错:
- from ..dir3 import b2
- ValueError: attempted relative import beyond top-level package
报错原因稍后解释. 现在在交互式模式下导入, 或者使用 python -m dir1.dir2.a1 的方式执行.
>>> import dir1.dir2.a1
以下几个示例都如此测试.
在 a1.py 中导入包 dir3:
from .. import dir3
在 a1.py 中导入 dir1.dir2.a2, 也就是同目录下的 a2.py:
from . import a2
导入模块的属性, 如变量 x:
- from ..dir3.b2 import x
- from .a2 import x
相对路径导入陷阱
前面说过一个相对路径导入时的错误:
- from ..dir3 import b2
- ValueError: attempted relative import beyond top-level package
dir3 明明在 dir1 下, 在路径相对上, dir3 确实是 a1.py 的../dir3, 但执行 python a1.py 为什么会报错?
from ..dir3 import b2
这是因为文件系统路径并不真的代表包的相对路径, 当在 dir1/a1.py 中使用..dir3,python 并不知道包 dir1 的存在, 因为没有将它导入, 没有声明为模块变量, 同样, 也不知道 dir2 的存在, 仅仅只是根据语句直到了 dir3 的存在. 但因为使用了相对路径, 不会搜索 sys.path, 所以它的相对路径边界只在本文件. 所以, 下面的导入也是错误的:
from . import a2
实际上, 更标准的解释是, 当 py 文件作为可执行程序文件执行时, 它所在的模块名为__main__, 即__name__为__main__, 但它并非一个包, 而是一个模块文件, 对它来说没有任何相对路径可言.
解决方法是显式导入它们的父包, 让 python 记录它的存在, 只有这样才能使用..:
python -m dir1.dir2.a2
还有几个常见的相对路径导入错误:
from .a3 import x
错误:
ModuleNotFoundError: No module named '__main__.a3'; '__main__' is not a package
原因是一样的, py 文件作为可执行程序文件执行时, 它所在的模块名为__main__, 它并非一个包.
最后, 建议在条件允许的情况下, 使用绝对路径导入, 而不是相对路径.
使用别名导入
通过包的导入方式也支持别名. 例如:
- from dir1.dir2.a2 import x as xx
- print(xx)
- import dir1.dir2.a2 as a2
- print(a2.x)
- from dir1.dir2 import a2 as a22
- print(a22.x)
来源: https://www.cnblogs.com/f-ck-need-u/p/9961372.html