1-Python - 模块那些事儿-下回
必要的准备
先跟我一样,建立这样的一个目录结构和空文件。
.\module
├─ dir1
│ ├─ a.py
│ ├─ b.py
│ └─ __init__.py
├─ dir2
│ ├─ a.py
│ ├─ b.py
│ └─ __init__.py
├─ x.py
├─ y.py
└─ __init__.py
before
目前为止,我们学习模块都是围绕单个模块展开的。这是模块的一般用法,接下来我们来聊点关于模块高级点的话题————包。
什么是包导入
导入模块时,除了导入模块名之外,Python还支持导入指定的目录路径,该指定的目录路径称为包,导入这种指定目录路径称为包导入。其实,包导入就是把该指定目录路径变成Python的命名空间,该指定目录路径内的子目录或者模块文件是命名空间中的属性。
包如何导入
前提是这个module目录是存在于Python的模块搜索路径中。
编写代码:
# dir1\a.py
x = 5
y = 6
现在,如果在x.py
中导入dir1\a.py
中的x
属性,那么使用import导入时,需要列出路径名,路径分隔符用点号分割:
# x.py
from dir1.a import x
print(x)
import dir1
print(dir1.a.y)
上例的from语句,可以"翻译"为,从dir1
目录中的a.py
模块中导入其属性x
。而import语句则是将dir1
目录导入,然后通过.
的方式调用其内a
中的y
属性。
另外,下面这些导入都是非法的:
# x.py
from .dir1 import a # ModuleNotFoundError: No module named '__main__.dir1'; '__main__' is not a package
import D:\tmp\module\dir1\a.py # SyntaxError: invalid syntax
import y.py # ModuleNotFoundError: No module named 'y.py'; 'y' is not a package
包初始化
你可能对各个目录中的__init__.py
文件感到疑惑?它到底是干嘛的?这就要好好说道说道了。
在选择包导入的时候,就必须遵循一条约束,包导入路径中的每个目录中都要有__init__.py
文件,否则包导入失败,如module的目录结构中,dir1
和dir2
都必须包含__init__.py
这个文件,而moudule
目录下则无需拥有该文件(虽然我们创建了,但在这里它不是必须的),因为module
目录不在导包的语句中,如下面这样的导入,都没有module:
# x.py
from dir1.a import x
import dir1
包的构建和导入遵循以下几项约束:
- 包中必须有
__init__py
文件。 - 在当前案例中,
module
相当于容器,它不需要__init__.py
文件,如果存在Python也会忽略它。 module
目录必须存在于sys.path
中,如下面的示例可以证明。
# x.py
import sys
print(sys.path[0]) # D:\tmp\module
__init__py
文件相当于声明文件,尽管它很多时候是空的。该文件的存在防止有同名的普通目录存在于sys.path
中,Python通过__init__.py
文件可以区分谁是Python的包,谁是普通目录。
换句话说,如果一个普通的目录中存在__init__.py
文件,那它就不在普通了,它成了Python的包。
一般的,__init__.py
文件扮演了包初始化的钩子,帮助包生成模块的名称空间等操作。
如我们在dir1\__init__.py
添加一行代码,然后当你引用dir1
包时,会首先触发其内的__init__.py
文件的执行:
# dir1\__init__.py
print("initializer dir1")
# x.py
import dir # initializer dir1
如上例,根据包的这个特性,我们可以在__init__.py
中做一些初始化的操作,比如创建数据库的连接,生成一些初始化的数据等操作。
包中的导入语句
一般地,我们在包导入中,通常使用from语句,因为import语句和包一起使用,不太方便。
来个示例,在x.py
中引用dir1\a.py
中的x
属性:
# x.py
from dir1.a import x
print(x) # 5
如上例,使用from语句清晰明了。
再来看使用import语句导入,它的情况就比较多了:
# x.py
import dir1
print(dir1.a.x) # 5
如上例,import语句只导入到包名这一级别,想要使用某个模块中的某个属性,一路.
下去即可。
# x.py
import dir1.a.x # ModuleNotFoundError: No module named 'dir1.a.x'; 'dir1.a' is not a package
如上例,使用import语句直接导入到具体的某个属性,但很明显报错了,显然不能这么导入。
# x.py
import dir1.a
# print(a.x) # NameError: name 'a' is not defined
如上例,使用import语句导入到模块这一级别,虽然导入语句不报错,但引用模块内的属性时却报了错,也显然不能这么用。
那么既然导入到模块这一级别不报错,引用时出了问题,那么就针对性的修改引用语句呗!往下看:
# x.py
import dir1.a
print(dir1.a.x) # 5
如上例,从包开始导入,一路点下去,解决问题。也可以使用下面的解决方式:
import dir1.a as a
print(a.x) # NameError: name 'a' is not defined
print(dir1.a.x)
如上例,使用as
语句解决问题。
为什么使用包导入
首先包在大型应用中起到比单一模块更多的信息。
比如下面这样的导入:
import db
import database.server.db
上例中,第二行的导入,比第一行导入提供了更多的信息。
其次,包导入提供了统一的接口,并解决了模糊性。如果多个包都在同一个目录下,想象module目录,其内的dir1和dir2目录内各有a和b两个模块。如果没有包来加以分割的话,就比较混乱了。
我们在脚本中,想要使用不同的功能时,通过不同的包名称就可以区分使用的是具体的文件,让导入的文件更加明确:
# x.py
import dir1.a
import dir1.b
import dir2.a
相对导入和绝对导入
在之前的例子中,我们都使用的是包的绝对导入,在使用绝对导入时,Python解释器是从模块搜索路径中开始查找的。
而一个包,其内的各个模块之间也会有相互导入的情况,包内的导入就无需走模块搜索路径了,比如上面的dir1
包中的a
、b
两个模块,a
导入b
中的属性,直接导入就行了,如下示例:
# dir1\b.py
z = 4
# dri1\a.py
import b
print(b.z) # 4
上例中的这种导入方式就是相对导入。而相对导入,则是先以自身为起点在同级目录开始查找。
当然,你也可以使用绝对导入:
# dir1\b.py
z = 4
w = 5
# dri1\a.py
from dir1 import b
print(b.w) # 5
注意,在Python 3版本中,优先使用相对导入,然后再走模块搜索路径。而Python 2中,则默认使用绝对导入,也就是说直接走模块搜索路径了,但Python 2从2020年开始不在受官方支持,所以这里不在多表。
that's all