7.1 模块
7.1.1 模块的导入
1、什么是模块
最常常见的场景,一个模块就是包含了一组功能的python文件,例如module.py,模块名是module
可以使用import module,四个通用类别:
A 使用python编写的.py文件
B 已被编译为共享库或DLL的C或C++扩展
C 把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)
D 使用C编写并链接到python解释器的内置模块
2、为什么要用模块
A 从文件级别组织程序,更方便管理
B 拿来主义,提升开发效率
3、如何使用模块-》import spam
A、第一次导入模块,会发生3件事,重复导入只会引用之前加载好的结果
a 产生一个新的名称空间
b 运行spam.py代码,产生的名字都存放于1的名称空间中,运行过程中global关键字指向的就是该名称空间
c 在当前名称空间拿到一个名字spam,该名字指向1的名称空间
引用spam.py中名字的方式:spam.名字
强调:被导入的模块在执行过程中使用自己独立的名称空间作为全局名称空间
B、起别名:import spam as sm
C、一行导入多个模块:import time,sys,spam
#spam.py print('from the spam.py') __all__=['read1','read2'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字 _money1=1001 #私有的,不能被import导入使用 money=1000 def read1(): print('spam模块:',money) def read2(): print('spam模块') read1() def change(): global money money=0 if __name__ == '__main__': # print(__name__) read1() read2() change() print('调试功能') #被导入模块有独立的名称空间 #测试一:money与spam.money不冲突 import spam money=1 print(spam.money) #1000 测试二:read1与spam.read1不冲突 def read1(): print('from current') # spam.read1() #spam模块中read1的函数 spam.read2() #read2调用的是spam模块中read1的函数 #测试三::执行spam.change()操作的全局变量money仍然是spam中的 money=1 spam.change() print(money) #0 spam.read1() #spam模块:0 #未模块名起别名 engine=input('>>: ') if engine == 'mysql': import mysql as sql elif engine == 'oracle': import oracle as sql sql.parse() #一行导入多个模块 import time,sys,spam
4、如何使用模块-》from 模块名 import 名字
唯一的区别就是:使用from...import...则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:spam.
优点:引用时不用加前缀,简单
缺点:容易与当前名称空间的名字冲突
from spam import money as m
from spam import money,read1,read2,change
from spam import * #*包含除了下划线开头以外所有的名字
from spam import money,read1,read2,change #测试一 money=1 print(money) #1 #测试二: 如果当前有重名read1或者read2,那么会有覆盖效果。 read1='read1' print(read1) #'read1' #测试三: 当前位置直接使用read1就好了,执行时,仍然以spam.py文件全局名称空间 money=1 read1() #spam模块:1000 #测试四: 当前位置直接使用read2就好了,执行时,仍然以spam.py文件全局名称空间 def read1(): print('from current func') read2()#调用spam模块中read1函数 #测试五:导入的方法在执行时,始终是以源文件为准的 money=1 change() #改变的是spam模块中的money编辑 print(money) # 1 # from...import * from spam import * #导入所有模块 read1=1 print(money,read1,read2,change) # 一行导入多个名字 from spam import read1,read2,money # 也支持as from spam import read1 as read # from spam import _money1 print(_money1) #报错,导入失败
7.1.2 模块的重载(了解)
考虑到性能的原因,每个模块只被导入一次,放入字典sys.module中,如果你改变了模块的内容,你必须重启程序,python不支持重新加载或卸载之前导入的模块,
有的同学可能会想到直接从sys.module中删除一个模块不就可以卸载了吗,注意了,你删了sys.module中的模块对象仍然可能被其他程序的组件所引用,因而不会被了解。
特别的对于我们引用了这个模块中的一个类,用这个类产生了很多对象,因而这些对象都有关于这个模块的引用。
如果只是你想交互测试的一个模块,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),这只能用于测试环境
#aa.py def func1(): print('func1')
import time,importlib import aa time.sleep(20) # importlib.reload(aa) aa.func1()
在20秒的等待时间里,修改aa.py中func1的内容,等待test.py的结果。
打开importlib注释,重新测试
7.1.3 py文件区分两种用途:模块与脚本
#编写好的一个python文件可以有两种用途: 一:脚本,一个文件就是整个程序,用来被执行 二:模块,文件中存放着一堆功能,用来被导入使用 #python为我们内置了全局变量__name__, 当文件被当做脚本执行时:__name__ 等于'__main__' 当文件被当做模块导入时:__name__等于模块名 #作用:用来控制.py文件在不同的应用场景下执行不同的逻辑 if __name__ == '__main__': pass
7.1.4 模块的搜索路径
内存----》内置模块-----》sys.path
#模块的查找顺序
1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看
2、如果没有,解释器则会查找同名的内建模块
3、如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。
7.1.5 编译python文件(了解)
为了提高加载模块的速度,强调强调强调:提高的是加载速度而绝非运行速度。python解释器会在__pycache__目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。通常会包含python的版本号。例如,在CPython3.3版本下,spam.py模块会被缓存成__pycache__/spam.cpython-33.pyc。这种命名规范保证了编译后的结果多版本共存。
Python检查源文件的修改时间与编译的版本进行对比,如果过期就需要重新编译。这是完全自动的过程。并且编译的模块是平台独立的,所以相同的库可以在不同的架构的系统之间共享,即pyc使一种跨平台的字节码,类似于JAVA火.NET,是由python虚拟机来执行的,但是pyc的内容跟python的版本相关,不同的版本编译后的pyc文件不同,2.5编译的pyc文件不能到3.5上执行,并且pyc文件是可以反编译的,因而它的出现仅仅是用来提升模块的加载速度的,不是用来加密的。
#python解释器在以下两种情况下不检测缓存 #1 如果是在命令行中被直接导入模块,则按照这种方式,每次导入都会重新编译,并且不会存储编译后的结果(python3.3以前的版本应该是这样) python -m spam.py #2 如果源文件不存在,那么缓存的结果也不会被使用,如果想在没有源文件的情况下来使用编译后的结果,则编译后的结果必须在源目录下 sh-3.2# ls __pycache__ spam.py sh-3.2# rm -rf spam.py sh-3.2# mv __pycache__/spam.cpython-36.pyc ./spam.pyc sh-3.2# python3 spam.pyc spam #提示: 1.模块名区分大小写,foo.py与FOO.py代表的是两个模块 2.你可以使用-O或者-OO转换python命令来减少编译模块的大小 -O转换会帮你去掉assert语句 -OO转换会帮你去掉assert语句和__doc__文档字符串 由于一些程序可能依赖于assert语句或文档字符串,你应该在在确认需要 的情况下使用这些选项。 3.在速度上从.pyc文件中读指令来执行不会比从.py文件中读指令执行更快,只有在模块被加载时,.pyc文件才是更快的 4.只有使用import语句是才将文件自动编译为.pyc文件,在命令行或标准输入中指定运行脚本则不会生成这类文件,因而我们可以使用compieall模块为一个目录中的所有模块创建.pyc文件 模块可以作为一个脚本(使用python -m compileall)编译Python源 python -m compileall /module_directory 递归着编译 如果使用python -O -m compileall /module_directory -l则只一层 命令行里使用compile()函数时,自动使用python -O -m compileall 详见:https://docs.python.org/3/library/compileall.html#module-compileall
7.2 包
7.2.1 什么是包
包就是一个包含了__init__.py文件的文件夹(可以往该文件夹下放一堆子模块) #需要强调的是: A. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错 B. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块 #注意事项: 1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的, 点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了, 点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。 2、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件 3、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
7.2.2 包的使用
注意:但凡是在导入时,出现 . ,这是导入包才有的语法,.的左边必须是一个包,使用的时候没有这种限制
包以及包所包含的模块都是用来被导入的,而不是被直接执行的。而环境变量都是以执行文件为准的
#包的使用之import import glance.db.models glance.db.models.register_models('mysql') #包的使用之from ... import ... #相对导入 from .m1 import f1 from .package2.m2 import f2 #绝对导入 from package1.m1 import f1 from package1.package2.m2 import f2