什么是模块
模块就是一系列功能的集合体,分为三大类
- 内置模块
- 第三方模块
- 自定义模块(一个py文件本身就是一个模块,文件名:m.py,模块名:m)
模块有四种形式 使用python编写的.py文件 已被编译为共享库或DLL的C或C++扩展 把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包) 使用C编写并链接到python解释器的内置模块
为何要有模块
原因一、内置和第三方模块拿来就用,无需定义,这种拿来主义,可以极大的提升自己的开发效率
原因二、自定义模块
- 可以将程序的各部分功能提取到一个模块供大家一起使用
- 好处是减少了代码冗余
第一次导入模块会发生3件事情:
- 产生该模块的的名称空间,将foo.py运行过程中产生的名字传给foo的名称空间中
- 执行foo.py
- 在当前文件内产生一个名字foo,把名字指向1中的名称空间
怎么用模块
引用:
用import语句导入多个模块,可以写多行import语句
- 引用foo内的变量:foo.x
- 引用foo内的函数:foo.get()
ps:此处对foo.py引用的名字不会和当前名称空间冲突
ps:可以用分隔符同行导入多个模块(不建议使用)
# 1. 首次导入模块 """ 首次导入模块会发生什么? 1. 执行被导入模块对应的文件 2. 在当前执行文件下产生以被导入模块名为命名的名称空间, 将被导入模块的文件运行的过程中产生的名字都丢到当前执行文件下的以被导入模块名为命名的名称空间中。 3. 在当前执行文件中产生的以被导入模块名为命名的名字,该名字指向2中产生的名称空间。 # 注意: 首次导入之后的导入都是直接引用首次导入的文件且名称空间不会重复执行代码。 import foo import foo import foo import foo import foo """
# 2. 引用模块: 如何引用? """ 强调一: 指名道姓地问某一个模块要名字对应的值,不会与当前名称空间的名字发生冲突。 print(foo.x) print(foo.get) print(foo.change) 强调二: 无论是查看还是修改操作的都是模块本身, 与调用位置无关。(提示: 名称空间的嵌套关系是在定义阶段就确定的。) import foo x = 33333333 foo.get() print(x) foo.change() print(x) """
# 3. 导入模块的两种书写风格 """ 建议如下所示导入多个模块: import time import os import foo 不建议在一行同时导入多个模块: import time, os, foo """
导入模块的规范:
前:python内置模块
中:第三方模块
后:程序员自定义模块
# 4. 导入模块的规范 """ 先写: Python内置模块。 再写: 第三方模块。 最后写: 程序员自定义模块。 import time import os import 第三方1 import 第三方2 import 自定义模块1 import 自定义模块2 import 自定义模块3 """
# 5. 为导入的模块起别名 """ 提示: as一般用于你的模块名比较长的情况下。 import abcdefghijkmnopqrstuvwxyz as super_man super_man.f1 super_man.f2 super_man.f3 """ # 6. 与函数对象一样,模块也是第一类对象。模块可以赋值, 模块可以当做参数, 可以当做返回值, 模块可以充当容器类型的元素。 # 7. 模块名的书写规范: 自定义的模块名应该采用纯小写加下划线的风格。 # 8. 可以在函数内导入模块 def func(): import foo
循环导入
循环导入就是模块1加载/导入2模块,2模块加载/导入3模块,3模块加载/导入模块,这就是循环导入的问题,由于第一个模块尚未加载完毕,所以引用失败、抛出异常,究其根源就是在python中,同一个模块只会在第一次导入时执行其内部代码,再次导入该模块时,即便是该模块尚未完全加载完毕也不会去重复执行内部代码
下面例子:
# m1.py print('正在导入m1') # from m2 import y def f1(): from m2 import y print(y) x='m1' # m2.py print('正在导入m2') # from m1 import x def f2(): from m1 import x print(x) y='m2' # run.py import m1 m1.f1()
#1、执行run.py会抛出异常 正在导入m1 正在导入m2 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/aa.py", line 1, in <module> import m1 File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module> from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module> from m1 import x ImportError: cannot import name 'x' #2、分析 先执行run.py--->执行import m1,开始导入m1并运行其内部代码--->打印内容"正在导入m1" --->执行from m2 import y 开始导入m2并运行其内部代码--->打印内容“正在导入m2”--->执行from m1 import x,由于m1已经被导入过了,所以不会重新导入,所以直接去m1中拿x,然而x此时并没有存在于m1中,所以报错
#2、执行文件不等于导入文件,比如执行m1.py不等于导入了m1 直接执行m1.py抛出异常 正在导入m1 正在导入m2 正在导入m1 Traceback (most recent call last): File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module> from m2 import y File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m2.py", line 2, in <module> from m1 import x File "/Users/linhaifeng/PycharmProjects/pro01/1 aaaa练习目录/m1.py", line 2, in <module> from m2 import y ImportError: cannot import name 'y' #2、分析 执行m1.py,打印“正在导入m1”,执行from m2 import y ,导入m2进而执行m2.py内部代码--->打印"正在导入m2",执行from m1 import x,此时m1是第一次被导入,执行m1.py并不等于导入了m1,于是开始导入m1并执行其内部代码--->打印"正在导入m1",执行from m1 import y,由于m1已经被导入过了,所以无需继续导入而直接问m2要y,然而y此时并没有存在于m2中所以报错
解决方案:
# 方案一:导入语句放到最后,保证在导入时,所有名字都已经加载过 # 文件:m1.py print('正在导入m1') x='m1' from m2 import y # 文件:m2.py print('正在导入m2') y='m2' from m1 import x # 文件:run.py内容如下,执行该文件,可以正常使用 import m1 print(m1.x) print(m1.y) # 方案二:导入语句放到函数中,只有在调用函数时才会执行其内部代码 # 文件:m1.py print('正在导入m1') def f1(): from m2 import y print(x,y) x = 'm1' # 文件:m2.py print('正在导入m2') def f2(): from m1 import x print(x,y) y = 'm2' # 文件:run.py内容如下,执行该文件,可以正常使用 import m1 m1.f1() 注意:循环导入问题大多数情况是因为程序设计失误导致,上述解决方案也只是在烂设计之上的无奈之举, 在我们的程序中应该尽量避免出现循环/嵌套导入,如果多个模块确实都需要共享某些数据, 可以将共享的数据集中存放到某一个地方,然后进行导入
搜索模块的路径与优先级
模块其实分为四个通用类别,分别是:
1、使用纯Python代码编写的py文件
2、包含一系列模块的包
3、使用C编写并链接到Python解释器中的内置模块
4、使用C或C++编译的扩展模块
在导入一个模块时,如果该模块已加载到内存中,则直接引用,否则会优先查找内置模块, 然后按照从左到右的顺序依次检索sys.path中定义的路径,直到找模块对应的文件为止, 否则抛出异常。sys.path也被称为模块的搜索路径,它是一个列表类型 # 无论是import还是from...import在导入模块时都涉及到查找问题 # 优先级: # 1、从内存找(内置模块) # 2、按照sys.path中存放的文件的顺序依次查找要导入的模块 import sys # 值为一个列表,存放了一系列的文件夹, # 其中一个文件夹是当前执行文件所在的文件夹 print(sys.path) # 了解:sys.modules查看已经加载到内存中的模块 # 针对模块的优化方案 import sys import foo def func(): import foo func() print(sys.modules) # 找foo.py就把foo.py的文件夹临时添加到环境变量中 # 然后导入foo模块,程序结束append添加的环境变量就会被删除 sys.path.append(r'文件夹路径')
区分py文件的两种用途
一个Python文件有两种用途,一种被当主程序/脚本执行,另一种被当模块导入,
为了区别同一个文件的不同用途,每个py文件都内置了__name__变量,
该变量在py文件被当做脚本执行时赋值为“main”,在py文件被当做模块导入时赋值为模块名
#foo.py ... if __name__ == '__main__': print('文件被执行') # foo.py被当做脚本执行时运行的代码 else: print('文件被导入') # foo.py被当做模块导入时运行的代码
编写一个规范的模块
我们在编写py文件时,需要时刻提醒自己,该文件既是给自己用的,也有可能会被其他人使用,因而代码的可读性与易维护性显得十分重要,为此我们在编写一个模块时最好按照统一的规范去编写
"The module is used to..." #模块的文档描述 import sys #导入模块 x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能 class Foo: #定义类,并写好类的注释 'Class Foo is used to...' pass def test(): #定义函数,并写好函数的注释 'Function test is used to…' pass if __name__ == '__main__': #主程序 test() #在被当做脚本执行时,执行此处的代码