小伙伴们学习都很努力,经过一段时间Python基础知识的学习,已经迫不及待的想要一展身手。有一位小伙伴叫小帅,要完成这样的功能:“从一批存储不同业务数据的Excel文件中读取数据,然后对读取到的数据进行简单的转换处理,最后再写入到数据库。”需求挺简单的,小伙伴就开始写代码,写着写着,发现好几个函数好长呀,都是好几百行的代码,整个py文件有1千多行的代码。代码终于写完了,开始要用真实数据进行调试,这时候发现调试很不熟手。“感觉好乱呀,一会儿要用鼠标拖动滚动条到文件最上面位置,查看读取Excel数据的代码逻辑,一会儿又要拖动到文件中间位置查看数据转换后的格式为什么不对。”
小帅就来问飞哥,“一个文件里面的代码太多了,函数也很长,看代码以及调试代码的效率好低,应该怎么优化?”
飞哥说,“你这问题,很多Python新手小伙伴都会遇到,包括飞哥自己以前也是这么干的。要解决这问题也很简单,学习了今天介绍的模块和包的Python知识后,你就知道怎么做了。”
接下来,飞哥就给小伙伴详细聊聊Python的模块和包,它是一个很重要的知识点,能够帮助小伙伴有效的提升模块化编程思维,提高代码的可复用性。
01 模块在Python中,一个.py文件就是一个模块,模块名就是.py文件的文件名称。通过导入模块的方式,模块里面的函数以及变量等对象就可以被其它py文件使用。
正是由于模块可导入的特性,可以把一个几千行的Py文件,通过重构的方式,将不同功能的代码分离,分别放在不同的Py文件里,就可以把一个大文件分割成几个小的只有几百行的Py文件。这样,保持了每个模块文件的代码功能的单一性,从而提高代码的可维护性以及可复用性。通过不同功能模块的组合,就可以完成一个功能复杂的需求,这就像乐高积木一样。
现在我们就来现学现用,上面小帅同学的问题,就有了优化方案:
1) 将文件中的代码功能进行剥离,分成Excel读取功能、数据处理功能、数据入库功能,分别放入下图右边所示的3个模块文件中。
2)在主文件中,分别导入上述3个模块文件,调用相关模块的接口函数,完成整个需求的功能。
模块是一个功能独立的代码文件,要使用模块,必须先导入模块。每个模块被导入后,都会创建一个它自己的唯一的名称空间,通常模块名就代表了这个名称空间。因此,访问模块的变量、函数等属性时,必须使用模块名+点号+标识符的方式,基本语法如下所示:
模块名 .标识符1、导入模块的方法
首先,飞哥先定义一个很简单的测试模块,然后分别说明以下各种不同的导入模块的方法。
自定义模块hello.py的代码如下:
#!/usr/bin/python3# -*- coding: utf-8 -*-name = "hello.py"print("各位请注意,我来啦")def test1(): print("哈喽, 我是测试1") def test2(): print("哈喽, 我是测试2")
1)import 模块名
示例1,导入内置模块
>>> import math # 导入math模块>>> math.pow(2,3) # 调用math模块的pow函数8.0>>>
示例2,导入自定义模块
在main.py中使用时,按如下方式调用自定义模块:
# 导入模块import hello# 调用模块中的变量print(hello.name)# 调用模块中的函数hello.test1()hello.test2()
示例3,可以一次导入多个模块,但代码规范中不提倡这种方式,应该使用一行导入一个模块的方式:
# 不推荐的一行导入多个模块>>> import os, sys, math>>> # 推荐的是一行导入一个模块import mathimport osimport sys
2)import 模块名 as 别名
有时候,一个模块的名称很长,在使用的时候就很不方便,这时候就可以给它起一个别名(起一个简短的名称),简化模块名称。其实,有时候模块名称也不长,但为了书写方便或是惯例,导入模块的时候也会起一个很短的别名。
# 使用别名导入模块import numpy as np# 通过别名引用模块中的属性img = cv2.imread("./cat.jpg")emptyImage = np.zeros(img.shape, np.uint8)
另外一种情况,避免命名冲突。比如,在当前文件中有一个标识符(函数或是变量或是第三方模块)与我们自定义的模块名重复了,可以在导入模块时给它起一个别名,来解决名称冲突。
例如,在main.py中,定义一个hello函数,就会出现冲突:
import hellodef hello(): print("哈喽,main文件")# 这时候出现名称冲突,这个全局的hello函数的名称# 会覆盖hello模块的名称,导致下面的模块调用失败hello.test1()
上面的代码执行后,会出现下面的异常:
上面这种情况,其实模块已经导入,只是模块名称hello和模块本身失去了联系(失联状态),就如同擦肩而过的2个人,那个TA其实一直都在,只是你无法看到而已。
通过别名的方式,就可以解决上面的问题:
import hello as hidef hello(): print("哈喽,main文件")# 通过别名解决命名冲突hi.test1()
3)
from 模块名 import 标识符
很多时候,我们为了完成一个功能,要使用另一个模块中的2个函数而已,而且功能也比较简单。就可以直接导入模块中的属性,在使用的时候,就如同本地定义的变量一样直接使用,不需要以模块名+点号的方式来访问。注意,这种方式,由于没有命名空间的限制,属性被导入到当前上下文的命名空间里,可能出现名称覆盖或命名冲突的情况。
# 可以一次导入1个也可以导入多个# 导入多个属性时,属性之间用逗号分隔from hello import test1, test2# 直接访问模块中的属性test1()test2()
4)from 模块名 import *
将模块中所有可导出的属性都导入到当前上下文的命名空间。在使用的时候,就如同本地定义的变量一样直接使用,不需要以模块名+点号的方式来访问。但是,由于被导入的属性很多,尤其是使用第三方模块时,你自己可能都不知道有哪些属性,因此,非常容易出现名称覆盖或命名冲突的情况。在代码规范中严禁使用这种导入方式。平时,在一些临时性的测试中,可以偶尔使用。
from hello import * # 直接访问模块中的属性test1()test2()
5)from 模块名 import 标识符 as 别名
它的作用与2)中的作用类似。一是解决命名冲突或覆盖,二是简化属性的名称或是简化包中子模块的名称。
示例1,导入模块的属性名称时使用别名:
from hello import test1 as hi1, test2 as hi2# 直接访问属性的别名hi1()hi2()
示例2,导入包的子模块时使用别名:
import numpy as npimport cv2 as cvfrom matplotlib import pyplot as plt #简化包中子模块的导入名称img = cv.imread('die.png')# 通过模块别名访问函数plt.imshow(img)
2、导入和加载
一个模块可以被导入(import)多次,但只会被加载一次。当模块被加载时,被导入模块的顶层代码将直接被执行,比如模块中定义的全局变量、类和函数的定义、全局的函数调用等等。
示例1,测试模块hello.py代码如下:
#!/usr/bin/python3# -*- coding: utf-8 -*-name = "hello.py"print("__name__的值:", __name__)print("各位请注意,我来啦")def test1(): print("哈喽, 我是测试1") def test2(): print("哈喽, 我是测试2")# 全局的函数调用test1()
在PyCharm中,当模块hello.py被直接运行时,输出结果如下:
在PyCharm中,当模块hello.py被导入多次,代码如下:
import helloimport helloimport helloprint(hello.name)
然后,运行这段代码,结果如下:
在Python自带的IDLE中也试试,进行多次导入:
通过3次不同的实验,说明以下结论:
1)当模块第1次被导入时,才被加载并执行模块内的语句。
2)以后再次导入模块时,Python解释器会判断模块是否被加载,如果已经被加载,则不会再执行加载过程,而是通过模块名直接引用已经加载的模块。
3)模块直接运行时的__name__的值是字符串“__main__”,模块被导入时的__name__的值是模块的名称(如果模块在是某个包中,__name__模块的名字有什么不同?小伙伴可以自己试试)。
因此,在模块文件中,不要出现全局的函数调用,因为全局的函数调用在加载时被直接执行。有小伙伴说,“写完一个模块,需要进行一个简单的测试,需要调用这些函数,怎么办?”这时候,根据上面的结论3),可以在下面的if语句中调用。
if __name__ == "__main__": # 在这里写相关的函数调用 pass
修改后的模块hello.py的代码如下:
#!/usr/bin/python3# -*- coding: utf-8 -*-name = "hello.py"def test1(): print("哈喽, 我是测试1") def test2(): print("哈喽, 我是测试2")if __name__ == '__main__': ''' 测试相关的代码写在这里,不要写在顶层的全局代码区 ''' print("各位请注意,我来啦") test1()
3、 模块的导入顺序
推荐所有的模块在Python模块的开头部分导入,一行导入一个模块。并且按照以下的导入顺序:
1)Python内置模块; 2)Python第三方模块; 3)自定义模块; 03 模块的搜索路径一个模块就是一个.py文件,当我们进行模块导入时,Python解释器是从哪里查找这个模块文件呢?
平时,我们安装一些开发软件时,会遇到添加到Path环境变量的选项,如果我们勾选后,当运行一些小工具时,直接在cmd窗口,输入工具的文件名称,回车就可以打开这个工具,比如我们的安装Python时,就有这个选项。如果没有勾选添加到环境变量,就要在工具所在的文件夹目录打开命令行窗口,输入工具的文件名称,回车才可以打开工具。
因此,Python查找模块文件时,很有可能也会在Path环境变量或是当前文件所在的文件夹目录中查找。事实上,Python确实会从这些位置查找模块文件,但是还有其他的一些目录,并遵循一定的搜索顺序。具体如下:
1)内存中查找是否存在要导入的模块,也就是查看模块是否已经加载过。
2)如果没加载过,在Python内置模块中查找;
3)如果没找到,当前执行文件所在的目录查找;
4)如果没找到,在sys.path所代表的环境变量指定的一系列目录中查找;
5)如果还没有找到,则Python抛出异常。
下面以hello模块为例,演示了这一查找过程。在实际开发过程中,如果我们的模块文件在其他文件夹,就需要使用sys.path.append()的方式添加,否则会弹出模块找不到的异常。
包,是一个包含其它模块或子包的目录。在Python3.3之前,目录下必须要有一个__init__.py文件,这个目录才被Python视为包。通常这个__init__.py文件是一个空文件。但在Python3.3及以后的版本,任何一个目录都可以被视为包,目录里可以有__init__.py文件,也可以没有。
一个典型的包的结构如下,这个包的名称是pkg1,包含3个子模块:mod1, mod2, mod3:
包有助于模块的结构化管理,在开发过程中,一个项目可能会有成百上千个Python文件,如果全放在一个文件夹里面,既不方便查找,也不方便管理。这时,包就派上用场了,可以建立层次化的包(目录)结构,把这些模块进行分类,功能相同或相关的模块放在一个目录(包)。
此外,一个软件项目,是由很多开发者共同开发,开发者A开发功能点1,开发者B开发功能点2,这2个开发者可以各自建立一个包,并把开发完成的模块放在包里面,即使2个开发者的模块名称相同,由于包名不同,访问时是由包名+模块名的方式访问,这也能解决模块名称冲突的问题。因此,使用包可以方便开发者进行协作开发。
1、 包的使用
包也是一种特殊的模块,和其它模块一样,包也使用包名+点号+标识符的方式访问属性。使用import或者from-import语句导入包中的模块。
对于上面的包pkg1中的模块1,定义了如下的函数:
# -*- coding: utf-8 -*-def mod1_test():print("我是模块1的函数")
导入和访问包中的模块,有如下方式:
1)import语句导入模块
# 导入模块import pkg1.mod1# 使用模块pkg1.mod1.mod1_test()
这种方式,调用模块的属性时,有很长的名称限制,比较繁琐,这时就可以使用from-import语句
2)From Import语句导入模块
# 导入模块from pkg1 import mod1# 使用模块mod1.mod1_test()
3)From Import语句导入模块中的具体属性
# 直接导入模块中的函数from pkg1.mod1 import mod1_test# 使用模块中的函数mod1_test()
注意:除了使用上面的标准方式访问模块或属性,还可以使用as来指定别名,小伙伴可以自己试试。
05 写在最后能够应用到实际工作中的知识才是有价值的知识。前面出现问题的小帅同学,飞哥特别叮嘱他消化一下模块和包的知识,并把前面的那张模块结构划分的图发给他,让他进行重构。过了1天,小帅发给飞哥修改后的代码,“优化掉20%的代码量,1个主文件完成模块的导入和功能的调用,3个模块文件分别完成文件读取数据、数据处理以及数据库操作。现在的代码结构很清晰,每个模块的功能独立。”
更多Python精彩文章、新手学习干货,欢迎一起交流学习!
END![6c0bf949bc8de35e6c8c85f2e78361e6.gif](https://i-blog.csdnimg.cn/blog_migrate/d113b1d7f37510ff8fd63a6ecd3c49d3.gif)
![096f6d27ef536e54ea252dfdab1f9f10.png](https://i-blog.csdnimg.cn/blog_migrate/3cba05c5d2b0a96d7970146b18825adc.jpeg)
![6c0bf949bc8de35e6c8c85f2e78361e6.gif](https://i-blog.csdnimg.cn/blog_migrate/d113b1d7f37510ff8fd63a6ecd3c49d3.gif)