Loading

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的目录结构中,dir1dir2都必须包含__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包中的ab两个模块,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
posted @ 2020-12-03 11:23  听雨危楼  阅读(93)  评论(0编辑  收藏  举报