【愚公系列】2021年12月 Python教学课程 17-模块与包

举报
愚公搬代码 发表于 2021/12/14 10:59:18 2021/12/14
【摘要】 一、 什么是模块在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在编程语言中,代码块、函数、类、模块,一直到包,逐级封装,层层调用。在Python 中,一个.py 文件就是一个模块,模块是比类更高一级的封装...

一、 什么是模块

在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。

在编程语言中,代码块、函数、类、模块,一直到包,逐级封装,层层调用。在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)

默认情况下,模块的搜索顺序是这样的:

  1. 当前执行脚本所在目录
  2. Python 的安装目录
  3. Python 安装目录里的 site-packages 目录

其实就是“自定义”——>“内置”——>“第三方”模块的查找顺序。任何一步查找到了,就会忽略后面的路径,所以模块的放置位置是有区别的。

在自定义模块的时候,对模块的命名一定要注意,不要和官方标准模块以及一些比较有名的第三方模块重名,一有不慎,就容易出现模块导入错误的情况发生。

__name__属性
一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。

if __name__ == '__main__':
    print('程序自身在运行')
else:
    print('我来自另一模块')

运行输出如下:

$ python using_name.py
程序自身在运行
$ python
>>> import using_name
我来自另一模块
>>>

说明: 每个模块都有一个__name__属性,当其值是’main’时,表明该模块自身在运行,否则是被引入。

说明:namemain 底下是双下划线, _ _ 是这样去掉中间的那个空格。

三、 包的概念

为了避免模块名冲突,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 的主要作用是:

  1. Python 中 package 的标识,不能删除
  2. 定义__all__用来模糊导入
  3. 编写 Python 代码(不建议在__init__中写 python 模块,可以在包中在创建另外的模块来写,尽量保证__init__.py 简单)

五、 模块打包发布

将自己创建好的模块和包打包成一个压缩文件,可以用于安装,也可以上传到网络供人下载使用,也可上传到 PyPI。

打包的过程:

  1. 将要打包的文件按照目录结构组织好
    a) 包
    b) py 文件
    c) readme.txt
    d) License 文件等等
  2. 在该目录下创建 setup.py 文件
  3. 编辑 setup.py 文件
  4. 在该目录命令行下运行 python setup.py sdist

setup.py 示例:

在这里插入图片描述
在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。