18. Python 中的模块与包
Hi, 大家好。我是茶桁。
这一段 Python 之旅怎么样?还算顺利吧?
之前我们都学习了些什么?有基本常识,流程,函数,不同类型的数据以及一些模块对吧?并且还做了一些练习来巩固所学过的内容。
那么今天,我们接着来学习模块。不过今天要学的模块和以往不太一样了,以前我们学习的都是 Python 内置的一些模块,而今天呢,我们自己来打包模块。
模块
简单点说,当我们定义一个 Python 文件,其后缀名为.py
的时候,那么这个文件就被称为模块。
模块中通常呢会定义一些相似的类、函数等代码内容,提供给别的程序引入使用。那对于应用,之前我们已经用过很多次了对吧?我们曾多次应用系统模块来使用,那这次,我们还是从系统模块开始吧。
系统模块
系统模块实际上就是一个 Python 的程序脚本,专门提供给我们自己的程序使用。它们是在安装好 Python 环境时,就已经存在的,需要的时候可以使用 import
导入到程序中使用。比如:
自定义模块
那知道了系统模块是什么东西,在理解自定义模块就轻松多了对吧?其实就是我们自己创建一个 Python 脚本,定义一些类或方法,供别的脚本导入后使用。
由于本节课比较特殊,所以课程源码除了
18.ipynb
这个笔记本文件之外,还有有一个文件夹,路径为./Python/packages/file
,然后内部会有多个.py
文件。
比如我们定义一个self.py
文件如下:
然后让我们在笔记本中引用这个文件(模块)以及其他模块,让我们来看看,还记得我们是怎么引入模块的嘛?来,回忆一下:
我们引入了一个系统模块time
,然后执行了一下模块里的time()
方法,并把最终结果打印了出来。
既然都已经有例子了,那我们有样学样来试试引入我们自己创建的文件:
报错了,告诉我们并没有self
这个模块。这个...
还记得我们刚才说过的文件路径嘛?./Python/packages/file
,而我们当前文件18.ipynb
是放在Python
目录下的,层级关系如下:
也就是说,我们要应用self.py
, 需要找对路径才行。那我们将路径加上去:
这回执行之后是没报错了,应该没问题了。
那下面呢,让我们来操作一下文件内的类、函数之类的试试:
没毛病,确实获取到了相关类病打印了出来。
可是我们这也太麻烦了,每次使用这个模块不是抖要输入这么长一段吧?packages.self.xxx
, 不知道之前的学习中大家有没有注意到一个关键字as
,这个我之前课程中都没有特意讲解过,但是在我们引入模块的时候会经常的用到。所以这里顺带讲一下吧,比如,我们在操作文件的时候有如下代码:
那这个as
我们能猜到是什么作用吗?其实,就是讲as
前的内容放入as
后面的这个变量里,然后将as
身后的这个变量改为一个对象而已,在这段代码里,我们打开了文件,并且将其放入了fp
这个变量里,变成了一个fp
对象。也可以理解为,我们将as
之前的内容起了一个别名。
那么我们导入文件的时候可以这么操作吗?我们试试看:
嗯,看来我们没搞错,确实可以这么用。
那让我们再来试试文件中的那个函数吧,函数内应该是执行了一段打印方法:
确实正确执行了。这也太顺利了,趁热打铁,让我们再来获取其中的变量:
导入模块其实不是仅可导入模块,还能从一个模块中导入类,方法甚至是变量:
应该能看出这一段代码的含义吧?就是from
(从)一个模块中import
导入一个对象。
模块中的测试代码
在自定义模块中,通常我们只是去定义类或函数,变量等,并不调用。如果在指定模块中,想要写一些测试代码,在当前模块作为主程序使用时执行,而作为模块被别的程序导入时不执行,那么可以把测试代码写到下面的代码块中:
那么这个模块再被别的程序调用之后,这段代码中的程序是不会被执行的。因为只有这个模块作为主程序运行时才会运行这段代码。我们来看下面这些操作就明白了:
按道理,我们引入模块之后应该会拿到该模块内的所有方法,可是刚才我们写的打印并没有被执行。现在我们在命令行内直接大概这个.py
文件来试试:
能看到,if
里面的print
被直接执行了,打印出了里面的字符串。
在这整段代码中,__name__
是一个特殊的变量,这个变量在当前脚本作为模块被别的程序导入时__name__
的值是当前这个模块的名称,也就是说,我在笔记本中导入的时候__name__
就是self
, 而我们在if
条件中的设定,是只有当前脚本被作为主程序直接由 Python 解析时才会进入判断,也就是__name__
这个变量的值为__main__
时。
我们来看看是不是如此,我们在self.py
中加上一段代码:
然后我们直接让self.py
在 Python 解释器中运行:
现在让我们在笔记本中重新引入一下模块中的变量name
,再打印出来看看:
打印的第一段内容为引入模块的时候,模块内的print(f'__name__: {name}')
执行了一次,第二段内容则是在笔记本中输入的方法print(name)
。 这样,我们就很直观的看到了__name__
在不同位置时存储了不同的值。
我们在写程序的时候要记得,不要想着把所有的方法定义在一个脚本文件内。
包
那什么是包呢?包并不是模块。你可以将包理解为一个文件夹,这个文件夹里面包含了多个 Python 文件。
包的结构
包的使用方法
其实,我们在刚才所讲的内容中,已经给大家演示过了包的使用方法,不知道小伙伴们能不能反应过来到底是哪里?不知道也没有关系,让我们从头来好好的盘一下这件事。
我们之前在当前目录下创建了一个文件夹packages
, 里面有我们self.py
文件。实际上,这就是一个包了。
让我们将这个包搞的复杂一点,按照上面我们写的结构来增加一些文件,然后我们看看现在的目录结构:
我们可以看到,除了我们之前设定的文件之外,还有多出来一个文件夹__pycache__
以及文件self.cpython-310.pyc
, 这个文件夹和文件是当这个包内的文件存在引入关系的时候,自动生成的缓存文件。大家可以不用管。
下面我们来看具体的包使用方法,我们预先在a, b, c, d
这四个文件内都写入了一模一样的代码:
当然,方法名和打印的内容都和文件同名的。
然后我们回到18.ipynb
这个笔记本文件内,开始操作使用:
似乎并不行,我们好像并不能引用包来直接使用。那我们怎么办呢?前面我们介绍过一个引用的方法from ... import
,我们在使用包内的模块时,需要这样去引用。
可以看到,这回我们引用成功了。那我们之前也学到了,在引入模块的时候,也可以直接就引用模块内的方法和变量,模块在包内也可以如此使用:
那既然我们得到了这种方式来导入模块内的内容,同样的,包内层如果还存在一个包,而我们要使用子包里的模块,也是这样的导入方法:
看到了,同样能够正常使用。
那如果再过分点,我们要想导入c.py
里的函数可以吗?试试就知道了, 再使用.
多链接一层:
呐,完全没问题。
然后我们再反过来看最开始,其实呢,我们的第一种方法直接引用包不是不可以,这需要用到我们这个包内的__init__.py
文件。
__init__.py
是一个包内的初始化文件,可以说,没有这个文件,这只是一个文件夹,只有有了这个文件,这才是一个包。在初始化的时候,就把包内的模块导入一次,在__init__.py
中写下以下代码:
然后我们再回到笔记本文件中直接导入包来使用试试:
这样就可以了。
好了,那如果这个时候我packages
这个包里一大堆的模块,我不想一个个的来导入,有什么办法吗?也是有的,我们需要用到__all__
这个参数,在__init__.py
中将包内所有的模块名做成一个列表,然后赋值给__all__
这个变量,那么我们在引入包内的模块的时候,就可以使用``*`来代表所有文件:
__init__.py
文件:
然后进行引入:
这样,我们就一次性导入了packages
这个包里的所有文件。
导入方式的分类
之前我们讲的内容中,把导入的方式都过了一遍。到现在这个位置,我们应该总结一下了。
具体的导入方式,我们可以将其分为两个类别,分别是绝对导入
和相对导入
。那两者有什么区别呢?
绝对导入
绝对导入的方式会使用「搜索路径」去查找和导入指定的包或模块,包括以下几种方式:
import module
导入模块import package
导入包import package.module
导入包.模块from module import func
从模块中导入函数from package import module
从包中导入模块from package.module import func
从包.模块中导入函数
关于「搜索路径」,我们先简单的理解一下就是,从当前文件夹中去找,如果找不到,就会去 Python 的安装环境中去寻找。
相对导入
⚠️ 相对导入智能在非主程序的模块中使用,不需要直接运行的模块文件。比如:
from .包名/模块名 import 模块/内容
from ..包名/模块名 import 模块/内容
.
和..
我们之前已经了解过了,.
代表的就是当前这一级,..
代表的就是上一级。
举个栗子好理解:假设我们现在去修改一下ps/c.py
这个文件,在这个模块中如果需要当前包中的d
模块:
注意啊,我们这个时候不要在c.py
中直接运行funcd()
方法,这样会导致报错:
那我们需要怎么运行呢?我们需要讲c.py
导入到其他文件中再执行。比如我们进入到笔记本18.ipynb
中导入执行。
然后让我们再在c.py
中加上一段内容:
小伙伴们应该都看出来了,我是在引用c.py
的上一级的a.py
。
让我们再在笔记本中执行一下试试:
这样,在我们引入了模块c
之后,我们同时也拥有了c.py
引入的同级和上一级中的d.py
、a.py
。
搜索路径
刚才我们简单提到了一下「搜索路径」, 这里我们详细的来展开说一下。
「搜索路径」就是在导入模块或者包的时候,程序查找的路径。主要的搜索路径包含以下三部分:
当前导入模块的程序所在的文件
python 的扩展目录中
python 解释器指定的其它 第三方模块位置
/lib/sitepackages
当然,如果你像我一样,系统中安装了多个 Python 版本,并且使用虚拟环境。那么你的「搜索路径」就不一定是在哪里了。那么我们到底该如何查找呢?我们来看一下以下查找方法:
我们看到找到的搜索路径被以列表的形式呈现出来。当然,我们找到搜索路径后,其实是可以向其中添加一个的。
单入口程序
那什么是单入口程序呢?顾名思义,这种程序就只有一个入口。那单入口程序就是指整个程序都是经过一个主程序文件在运行,其它程序都封装成了包或模块。
单入口文件是作为程序直接被运行的唯一文件,其他都是作为模块或者包,被导入单入口中去执行。打个比方说,我们要去做一个 ATM 机的程序,我们来实现一个单入口程序。那么可能的情况如下:
那么这个程序中,main
就是程序的主入口文件,会被直接作为主程序运行。所以main.py
文件必须使用「绝对导入」的方式。
好,那讲到这里,我们今天的内容也就结束了。不知道小伙伴们理解了多少?
本节课也不太好放练习,那我们这节课就免了。下去之后,大家去拉取我的源码好好的研究一下引入关系,然后讲包、模块的概念好好的理解透。
那小伙伴们,咱们下节课再见。
版权声明: 本文为 InfoQ 作者【茶桁】的原创文章。
原文链接:【http://xie.infoq.cn/article/604e836f45bc8f8fb6e8e7a4d】。文章转载请联系作者。
评论