【愚公系列】2021年12月 Python教学课程 17-模块与包
一、 什么是模块
在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。
在编程语言中,代码块、函数、类、模块,一直到包,逐级封装,层层调用。在Python 中,一个.py 文件就是一个模块,模块是比类更高一级的封装。模块是一个包含所有你定义的函数和变量的文件。被导入的模块也通常称为库。
模块可以分为自定义模块、内置模块和第三方模块。自定义模块就是你自己编写的模块,如果你自认水平很高,也可以申请成为 Python 内置的标准模块之一!如果你在网上发布自己的模块并允许他人使用,那么就变成了第三方模块。内置模块是 Python“内置电池”哲学的体现,在安装包里就提供了跨平台的一系列常用库,涉及方方面面。第三方模块的数量非常庞大,有许多非常有名并且影响广泛的模块,比如 Django。
使用模块有什么好处?
- 首先,提高了代码的可维护性。
- 其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他的模块引 用。不要重复造轮子,我们简简单单地使用已经有的模块就好了。
- 使用模块还可以避免类名、函数名和变量名发生冲突。相同名字的类、函数和变量完全可以分别存在不同的模块中。但是也要注意尽量不要与内置函数名 (类名)冲突。
二、 模块的使用
要在我们的程序中,使用其它的模块,就必须先导入对应的模块。在 Python 中,模块的导入方式有以下四种:
- import xx.xx
- from xx.xx import xx
- from xx.xx import xx as rename
- from xx.xx import *
对于 xx.xx 的说明:
由于一个模块可能会被一个包封装起来,而一个包又可能会被另外一个更大的包封装起来,所以我们在导入的时候,需要提供导入对象的绝对路径,也就是“最顶层的包名.次一级包名.(所有级别的包名).模块名.类名.函数名”。类似文件系统的路径名,只是用圆点分隔的。
有时候,模块名就在搜索路径根目录下,那么可以直接 import 模块名,比如 Python内置的一些标准模块,os、sys、time 等等。
大多数时候,我们不需要直接导入到函数的级别,只需要导入到模块级别或者类的级别,就只需要使用 import Django.contrib.auth.models 导入 models 模块,以后使用models.User 来引用 models 模块中的类。
总之,对于 xx.xx,你想导入到哪个级别,取决于你的需要,可以灵活调整,没有固定的规则。
1. import xx.xx
这会将对象(这里的对象指的是包、模块、类或者函数,下同)中的所有内容导入。如果该对象是个模块,那么调用对象内的类、函数或变量时,需要以 module.xxx 的方式。
比如,被导入的模块 testmodule:
# testmodule.py
def func():
print("this is test module!")
在 Main.py 中导入 testmodule:
# Main.py
import testmodule
testmodule.func() # 调用方法
2. from xx.xx import xx.xx
从某个对象内导入某个指定的部分到当前命名空间中,不会将整个对象导入。这种方式可以节省写长串导入路径的代码,但要小心名字冲突。
在 Main.py 中导入 testmodule:
# Main.py
from testmodule import func
testmodule.func() # 错误的调用方式
func() # 这时需要直接调用 func
3. from xx.xx import xx as rename
为了避免命名冲突,在导入的时候,可以给导入的对象重命名。
# Main.py
from testmodule import func as f
def func(): ## main 模块内部已经有了 func 函数
print("this is main module!")
func()
f()
4. from xx.xx import *
将对象内的所有内容全部导入。非常容易发生命名冲突,请慎用!
# Main.py
from testmodule import *
def func():
print("this is main module!")
func() # 从 module 导入的 func 被 main 的 func 覆盖了
执行结果:this is main module!
模块搜索路径
不管你在程序中执行了多少次 import,一个模块只会被导入一次。这样可以防止一遍又一遍地导入模块,节省内存和计算资源。那么,当使用 import 语句的时候,Python解释器是怎样找到对应的文件的呢?
Python 根据 sys.path 的设置,按顺序搜索模块。
>>> import sys
>>> sys.path
['D:\\PycharmProjects\\VIPClassDemos', 'D:\\PycharmProjects\\VIPClassDemos',
'D:\\Programs\\Python\\Python36\\python36.zip',
'D:\\Programs\\Python\\Python36\\DLLs', 'D:\\Programs\\Python\\Python36\\lib',
'D:\\Programs\\Python\\Python36', 'D:\\Programs\\Python\\Python36\\lib\\sitepackages']
当然,这个设置是可以修改的,就像 windows 系统环境变量中的 path 一样,可以自定义。 通过 sys.path.append(‘路径’)的方法为 sys.path 路径列表添加你想要的路径。
import sys
import os
new_path = os.path.abspath('../')
sys.path.append(new_path)
默认情况下,模块的搜索顺序是这样的:
- 当前执行脚本所在目录
- Python 的安装目录
- Python 安装目录里的 site-packages 目录
其实就是“自定义”——>“内置”——>“第三方”模块的查找顺序。任何一步查找到了,就会忽略后面的路径,所以模块的放置位置是有区别的。
在自定义模块的时候,对模块的命名一定要注意,不要和官方标准模块以及一些比较有名的第三方模块重名,一有不慎,就容易出现模块导入错误的情况发生。
__name__属性
一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。
if __name__ == '__main__':
print('程序自身在运行')
else:
print('我来自另一模块')
运行输出如下:
$ python using_name.py
程序自身在运行
$ python
>>> import using_name
我来自另一模块
>>>
说明: 每个模块都有一个__name__属性,当其值是’main’时,表明该模块自身在运行,否则是被引入。
说明:name 与 main 底下是双下划线, _ _ 是这样去掉中间的那个空格。
三、 包的概念
为了避免模块名冲突,Python 又引入了按目录来组织模块的方法,称为包(Package),包是模块的集合,比模块又高一级的封装。没有比包更高级别的封装,但是包可以嵌套包,就像文件目录一样,如下图:
最顶层的 Django 包封装了 contrib 子包,contrib 包又封装了 auth 等子包,auth 又有自己的子包和一系列模块。通过包的层层嵌套,我们能够划分出一个又一个的命名空间。
包名通常为全部小写,避免使用下划线。
四、 包的使用
只有包含__init__.py 文件的目录才会被认作是一个包!
上图中的 example、p1 和 p2 都是包,因为它们目录内都有__init__.py 文件,并且 p1和 p2 是 example 的子包。
init.py 可以是空文件,也可以有 Python 代码,init.py 本身就是一个模块,但是要注意,它的模块名是它所在的包名而不是__init__。
就上图,举个包和模块之间调用的例子:
# example\p1\x.py
def show():
print("this is module x")
# example\p2\a.py
import example.p1.x
def show():
print("this is modula a")
example.p1.x.show()
show()
运行 a.py 的结果:
this is module x
this is modula a
设想一下,如果我们使用 from example.p1 import *会发生什么?
Python 会进入文件系统,找到这个包里面所有的子模块,一个一个的把它们都导入进来。 但是这个方法有风险,有可能导入的模块和已有的模块冲突,或者并不需要导入所有的模块。为了解决这个问题,需要提供一个精确的模块索引。这个索引要放置在__init__.py 中。
如果包定义文件__init__.py 中存在一个叫做__all__的列表变量,那么在使用 from package import *的时候就把这个列表中的所有名字作为要导入的模块名。例如在 example/p1/init.py 中包含如下代码:
__all__ = ["x"]
这表示当你使用 from example.p1 import *这种用法时,你只会导入包里面的 x 子模块。从上边的例子可以看出,init.py 的主要作用是:
- Python 中 package 的标识,不能删除
- 定义__all__用来模糊导入
- 编写 Python 代码(不建议在__init__中写 python 模块,可以在包中在创建另外的模块来写,尽量保证__init__.py 简单)
五、 模块打包发布
将自己创建好的模块和包打包成一个压缩文件,可以用于安装,也可以上传到网络供人下载使用,也可上传到 PyPI。
打包的过程:
- 将要打包的文件按照目录结构组织好
a) 包
b) py 文件
c) readme.txt
d) License 文件等等 - 在该目录下创建 setup.py 文件
- 编辑 setup.py 文件
- 在该目录命令行下运行 python setup.py sdist
setup.py 示例:
- 点赞
- 收藏
- 关注作者
评论(0)