第七课时之模块
6.模块
如果你退出python解释器并重新进入,你做的任何定义(变量和方法)都会丢失。因此如果你想要编写一些更大的程序,为准备解释器输入使用一个文本解释器会更好,并以哪个文本替代作为输入执行。这就是传说中的脚本。随着你的程序变的越来越长,你可能想要将它分割成几个更易于维护的文件。你也可以想在不同的程序中使用顺手的函数,而不是把代码在它们之间中拷来拷去。
为了满足这些要求,python提供了一个方法可以从文件中获取定义,在脚本或者解释器的一个交互式实例中使用。这样的文件被称为模块;模块中的定义可以导入到你一个模块或主模块中(在脚本执行时可以调用的变量集位于最高级,并且处于计算器模式)。
模块是包括python定义和声明的文件。文件名就是模块名加上.py后缀。模块的模块名(作为一个字符串)可以有全局变量__name__得到。例如你可以用自己惯用的文本编辑器在当前目录下创建一个叫fibo.py的文件,录入一下内容:
现在进入python解释器并使用以下命令导入这个模块:
这样做不会直接把fibo中的函数导入当前的语义表;它只是引入了模块名fibo。你可以通过模块名按如下方式访问这个函数:
如果打算频繁使用一个函数,你可以将它赋予一个本地变量:
6.1深入模块
除了包含函数定义外,模块也可以包含执行语句。这些语句一般用来初始化模块、它们仅在第一次被导入的地方执行一次。
每个模块都有自己私有的符号表,被模块内所有的函数定义作为全局符号表使用。因此模块的作者可以在内部使用全局变量,无需担心他与某个用户的全局变量意外冲突。从另一个方面讲,如果你确切得知道自己在做什么,你可以使用引用模块函数的表示法访问模块的全局变量,modname.itemname。
模块可以导入其他的模块。一个(好的)习惯是将所有的import语句放在模块的开始(后者是脚本),这并非强制。被导入的模块会放入当前模块的全局符号表中。
import语句的一个变体直接从被导入的模块中导入命名到本模块的语义表中。例如:
这样不会从局域语义表中导入模块名(如上所示,fibo没有定义)。
甚至有种方式可以导入模块中的所有定义:
这样可以导入所有除了以下划线(_)开头命名。
需要注意的是在实践中往往不鼓励从一个模块或包中使用(*)导入所有,因为这样会让代码变得很难读。不过在交互会话中这样用很方便省力。
注解:
处于性能考虑,每个模块在每个解释器会话中只导入一遍。因此你修改了你的模块,需要重启解释器;或者如果你就是想交互式的测试这么一个模块,可以用imp.reload()重新加载,例如:import imp; imp.reload(modulename).
6.1.1作为脚本来运行模块
当你使用以下方式运行python模块时,模块中的代码便会被执行:
-python fibo.py <arguments>
模块中的代码会被执行,就像导入它一样,不过此时__name__被设置为”__main__”。这相当于,如果你在模块后加入如下代码:
就可以让此文件作为模块导入时一样作为脚本执行。此代码只有在模块作为”main”文件执行时才被调用:
如果模块被导入,不会执行这段代码:
这通常用来为模块提供一个便于测试的用户接口(将模块作为脚本执行测试需求)
6.1.2模块的搜索路径
导入一个叫fibo的模块时,解释器现在当前目录中搜索名为fibo.py的文件。如果没有找到的话,接着会到sys.path变量中给出的目录列表中查找。Sys.path变量的初始值来自如下:
输出脚本的目录(当前目录)
环境变量PYTHONPATH表示的目录列表中搜索(这和shell变量PATH具有一样的语法,即一系列目录名的列表)
Python默认安装路径中搜索
注解:在支持符号连接的文件系统中。输入的脚本所在的目录是符号连接指向的目录。换句话说也就是包含符号连接的目录不会被加到目录搜索路径中
实际上解释器由sys.path变量指定的路径目录搜索模块,该变量初始化时默认包含了输入脚本(或者当前目录),PATHONPATH和安装目录。这样就允许python程序了解如何修改或替换模块搜索目录。需要注意的是由于这些目录中包含有搜索路径中运行的脚本,所以这些脚本不应该和标准块重名,否则在导入模块是python会尝试把这些脚本当作模块来加载,这通常会引发错误。
6.1.3”编译的”python文件
为了加快加载模块的速度,python会在__pycache__目录下module.version.pyc名字缓存每个模块编译后的版本,这里的版本编制了编译后文件的格式。它通常会包含python的版本号。例如在CPython3.3版中,spam.py编译后的版本将缓存为__pycache__/spam.cpython-33.pyc。这种命名约定允许由不同发布和不同版本的python编译的模块同时存在。
部分高级技巧:
为了减少一个编译模块的大小,你可以在python命令行中使用-O或者-OO。-O参数删除了断言语句,-OO参数删除了断言语句和__doc__字符串。
来自.pyc文件或.pyo文件中的程序不会比来自.py文件的运行更快;.pyc或.pyo文件只是在它们加载的时候更快一些。
-compileall模块可以为指定目录中的所有模块创建.pyc文件(或者使用-O参数创建.pyc文件)。
6.2标准模块
Python带有一个标准模块库,并发布有独立的文档,名为python库参考手册(此后称其为”库参考手册”)。有一些模块内置于解释器之中,这些操作的访问接口不是语言内核的一部分,但是已经内置于解释器了。这既是为了提高效率,也是为了给系统调用等操作系统原生访问提供接口。这类模块集合是一个依赖于底层平台的配置选项。例如,winreg模块只提供在windows系统上才有。有一个具体的模块值得注意:sys,这个模块内置于所有的python解释器。变量sys.ps1和sys.ps2定义了主提示符和辅助提示符字符串:
变量sys.path是解释器模块搜索路径的字符串列表。他有环境变量PATHONPATH初始化,如果没有设定PATHONPATH,就由内置的默认值初始化。你可以用标准的字符串操作修改它:
6.3dir()函数
内置函数dir()用于按模块名搜索模块定义,它返回一个字符串类型的存储列表:
无参数调用时,dir()函数返回当前定义的命名:
注意该列表列出了所有类型的名称:变量,模块,函数等等
-dir()不会列出内置函数和变量名。如果你想列出这些内容,它们在标准模块builtins中定义:
6.4包
包通常是使用”圆点模块名”的结构化模块命名空间。例如。名为A.B的模块表示了名为A的包中名为B的子模块。正如同用模块来保存不同的模块架构可以避免全局变量之间的相互冲突,使用圆点模块名保存像NumPy或Python Imageing Library之类的不同类库架构可以避免模块之间的命名冲突。
需要注意的是使用from package import item方式导入包时,这个子项(item)既可以是包中定义的其他命名,像函数,类或变量。Import语句首先核对是否包中有这个子项,如果没有,它假定这是一个模块,并尝试加载他。如果没有找到它,会引发一个importError异常。
相反使用类似import item.subitem.subsubitem这样的语法时这些子项可以是包或模块,但不能是前面子项中定义的类,函数或变量。
6.4.1从*导入包
那么当用户写入from fibo import *时会发生什么事?理想中总是希望在文件系统中找出包中所有的子模块,然后导入它们。这可能会花掉很长时间,并且出现期待之外的边界效应,导出了希望只能显示导入的包。
对于包的作者来说唯一的解决方案就是给提供一个明确的包索引。Import语句按如下条件进行转换:执行from package import *时,如果包中的__init__.py代码定义了一个名为__all__的列表,就会按照列表中给出的模块名进行导入。新版本的包发布是作者可以任意更新这个列表。如果包作者不想import *时候导入它们的包中所有模块,那么也可能会决定不支持它(import *)。
6.4.2包内引用
如果包中使用了子包结构,可以按绝对位置从相邻的包中引入子模块。
你可以用这样的形式from module import name来写显式的相对位置导入。那些显式相对导入用点号标明关联导入当前和上级包。
需要注意的是显式或隐式相对位置导入都基于当前模块的命名。因为主模块的名字总是”__main__”,python应用程序的主模块应该总是用绝对导入。
6.4.3多重目录中的包
包支持一个更为特殊的特性,__path__。在包的”__init__.py”文件代码执行之前,该变量初始化一个目录名列表。该变量可以修改,它作用于包中的子包和模块的搜索功能
补充:
事实上函数定义既是”声明”又是”可执行体”;执行体由函数在模块全局语义表中的命名导入