主要内容:
- 模块的介绍
- 模块的两种导入方式
- 循环导入模块的问题及解决方法
- 绝对导入,相对导入
- 模块的查找顺序
- __name__, __file__的使用
- 软件开发规范
1 模块的介绍
模块相当于人们把已经写好的一个个功能给封装在一起,供别人调用的文件或文件夹。 模块通常有3种来源: 1. 内置的模块 如time, os, datetime等等 2. 第三方提供的 比如requests模块等等 3. 自己定义的模块 模块有4种表现形式: 1. 使用纯Python编写的py文件 2. 已被编译为共享库或DLL(动态链接库文件)的CC++扩展 3. 以包的形式把一系列py文件组织起来的,所谓包就是指包含有__init__.py文件的文件夹,导入包就是导入__init__.py这个文件 4. 使用C语言写好的并链接到Python解释器的内置模块 我们使用的模块的优点也很简单明了,Python语言的特性就是胶水语言,外号叫调包侠,使用他人编写好的模块能大大减少开发时间,并且使用多个模块能把功能拆分开来供别人分别使用,就像我们会把自己的文件夹分门别类的存放,而不会把所有文件夹统统放入一个文件夹。
2. 模块的两种导入方式
import ...
模块的第一种导入方式就是直接import关键字, 后面直接跟模块名称。 导入模块的过程通常发生了以下几个步骤: 1. 把被导入的模块的模块名导入到执行文件所在的名称空间 2. 执行被导入模块的所有代码,把产生的名称存放如被导入模块的名称空间 3. 执行文件所在位置就可以在任何地方通过模块名.名称来调用该名称所用的值了。 此外任何模块只会被导入一次,多次导入只有第一次会生效。 # t1.py name = 'test' print('this is t1.py') # t2.py import t1 name = 't2' print(t1.name) print(name) # out------------- this is t1.py test t2 查看输出结果确实在导入过程中就执行了模块内的代码,而且使用import导入后访问被导入模块的同名变量并不会和执行模块的变量冲突,而在第二种方式from ... import ... 就会出现名称冲突等现象。
from ... import ...
这个句式是from 模块名 import (模块/名称空间对应的名称)。当被导入文件功能太多,而我们可能只需要部分甚至很少的功能,我们就可以使用from ... import... 句式。使用from... import ... 的时候发生的事件与import 大同小异 1 执行被导入文件的代码 2 将产生的名称放入对应的名称空间里 3. 将我们需要的名称直接导入到执行文件所在的名称空间里, 这样调用就可以不用带模块名前缀了。 使用from句式导入模块有几个注意点 1. 我们是通过把被导入模块需要的名称直接导入到自己执行文件所在的名称空间,这样就可能存在名称冲突的可能性,所以需要慎重 2. from 句式 还有一种特殊的导入方式,就是通过from ... import * 把模块所有的名称导入到当前所在名称空间,这个写法就更需要慎重了,因为这样虽然调用变量时不用加模块名,但是我们只能通过点进模块进去查看我们到底导入了哪些名称。
当然,import * 所导入的名称可以通过 __all__ 这个列表来指定哪些可以被导入。 最后,模块被导入时我们可以使用as关键字来为名字特别长的模块来取别名, 例如这样的句式 from time import perf_counter as pc。
3. 循环导入
循环导入问题的产生,循环导入就是指代设计不合理造成的两个模块相互导入,由于导入模块会执行模块所在的代码,这可能会导致的名称在未定义之前就发生调用的问题。下面这个简单例子就说明了问题。 # m1.py import m2 a = 10 print('hello') # m2.py from m1 import a print(a) print('m2') # ----------out ------- ImportError: cannot import name 'a' 执行m2,会发现报导入错误的错误。这是因为Python执行代码时按照顺序执行的,首先第一步就是导入m1模块的a变量,
那么就会去执行m1文件的代码,但发现m1模块的第一行就是继续导入m2模块代码,
那么又跳去m2模块执行,此时就发现了问题模块只会在第一次导入执行代码,所以此时a变量就并没有被正确导入。 解决方法通常有两种 1. 保证对象导入之前就已经被定义了,那么上述代码可以修改为 # m1.py a = 10 import m2 print('hello') # m2.py from m1 import a print(a) print('m2') 10 m2 hello 10 m2 可以看到只是修改了m1文件a=10和导入m2模块的顺序就改正了bug,因为在导入m2之前a已经存在于m1文件的名称空间了,此时就已经拿到了a的值,因此可以正常执行了。 2. 将产生循环导入的代码写入函数内,那么这样就能够一定程度的避免循环导入了,但这种方式又改变了我们调用的方式,所以也不是很好。 总结一下循环导入,它产生的问题一般都是上述发生的在定义之前就导入名称空间使用了,这种问题应该在设计阶段就避免的。
4. 绝对导入,相对导入
绝对导入:
绝对导入是指需要以执行文件所在的sys.path的路径开始查找,其中执行文件导入的模块也都要以执行文件所在目录为起始目录开始查找,查不到就会报ImportError。
绝对导入的优点就是层次清晰,执行文件和其他模块写法都统一, 缺点就是其他模块导入要总是根据执行文件所在处来导入,很麻烦,且文件位置变动导入就会出错。
相对导入:
相对导入是指相对于被导入模块本身所在的文件夹目录为起始点开始查找,以.表示当前目录,.. 表示上层目录 ...表示上上层目录,依次类推。
它的优点就是导入简单,只要和导入的其他模块的相对位置不变,就不会报错。缺点就是没有绝对导入清晰,且不能作为执行文件,只能作为被导入模块。
5. 模块的查找顺序
Python中的模块查找路径是先从内存中查找模块,再从内置模块中,查找,最后再从sys.path中存储的路径查找。此处sys.path 返回的是一个列表,我们可以通过在sys.path路径中添加路径来增加查找路径。
6. __name__, __file__的使用
我们再写完模块需要测试模块内各个部分的功能,而这就需要在模块下面直接测试,而我们又不希望别人调用我们写的模块的时候也把这些测试代码执行了,也不希望自己把这些代码注释,
这就可以通过一个固定句式,利用到if __name__ == '__main__': 这个句法结构,这是因为模块级别的__name__ 在自己执行时的名字是__main__,而别人调用的时候这个__name__就是模块名称。 至于__file__则是指代模块的路径位置,这在拼接路径中很有用。
7. 软件开发规范
在软件开发中,规范就显得非常重要了,而我们通常都会遵照一套开发规范。就是我们要把代码按照功能分门别类,这样就能够非常好的分工合作,并且维护起来也容易。例如我们一般会建立这几个文件夹: bin目录 ---> 启动目录,里面会放入启动函数来执行代码 core目录 ---> 核心目录,存放我们核心逻辑的代码 conf目录 ---> 配置目录, 里面会存放我们项目的配置文件 db目录 ---> 数据相关目录,里面会存放项目所需要用到的数据 lib目录 ---> 公共库目录, 里面会存放项目需要用到的公共方法等接口 log目录 ---> 日志目录, 里面存放了项目运行时的日志信息等等