Python编程基础之十一模块

时间:2021-10-02 00:45:09

一、简介

        模块是用来组织Python代码的,而包则是用来组织模块的。模块是对一些函数的封装,以实现重复利用,模块可以被别的程序引入来使用该模块中的函数。
        模块支持从逻辑上组织Python代码。 当代码量变得相当大的时,最好把代码分成一些有组织的代码段,前提是保证它们的彼此交互。 这些代码片段相互间有一定的联系,可能是一个包含数据成员和方法的类,也可能是一组相关但彼此独立的操作函数。 这些代码段是共享的,所以Python允许"调入"一个模块, 允许使用其他模块的属性来利用之前的工作成果,实现代码重用。这个把其他模块中属性附加到你的模块中的操作叫做导入(import) 。那些自我包含并且有组织的代码片断就是模块( module )。

二、详解

1、模块和文件

       如果说模块是按照逻辑来组织Python代码的方法,那么文件便是物理层上组织模块的方法。因此一个文件被看作是一个独立模块,一个模块也可以被看作是一个文件, 模块的文件名就是模块的名字加上扩展名.py 。在Python中导入的是模块或模块属性。
(1)模块名称空间
        一个名称空间就是一个从名称到对象的关系映射集合,模块名称是它们的属性名称中的一个重要部分,例如string模块中的atoi()函数就是string.atoi()。给定一个模块名之后,只可能有一个模块被导入到Python解释器中,所以在不同模块间不会出现名称交叉现象,所以每个模块都定义了它自己的唯一的名称空间。即使属性之间有名称冲突,但它们的完整授权名称(fully qualified name),通过句点属性标识指定了各自的名称空间, 防止了名称冲突的发生。
(2)搜索路径和路径搜索
       模块的导入需要一个叫做"路径搜索"的过程, 即在文件系统"预定义区域"中查找mymodule.py文件(导入mymodule时)。 这些预定义区域只不过是Python搜索路径的集合。路径搜索和搜索路径是两个不同的概念,前者是指查找某个文件的操作,后者是去查找一组目录。
        有时候导入模块操作会失败,解释器会告诉你它无法访问请求的模块,可能的原因是模块不在搜索路径里,从而导致了路径搜索的失败。默认搜索路径是在编译或是安装时指定的。 它可以在一个或两个地方修改,一个是启动Python的shell或命令行的PYTHONPATH环境变量。 该变量的内容是一组用冒号分割的目录路径。 如果想让解释器使用这个变量, 那么请确保在启动解释器或执行 Python 脚本前设置或修改了该变量。
        解释器启动之后,也可以访问这个搜索路径,它会被保存在sys模块的sys.path变量里。不过它已经不是冒号分割的字符串,而是包含每个独立路径的列表。使用如下命令查看:

>>> import sys
>>> sys.path
        这只是个列表,所以可以随时随地对它进行修改。如果知道需要导入的模块是什么,而它的路径不在搜索路径里,那么只需要调用列表的 append()方法即可,如sys.path.append('/home/wesc/py/lib'), 修改完成后就可以加载自己的模块了。只要这个列表中的某个目录包含这个文件,它就会被正确导入。当然,这个方法是把目录追加在搜索路径的尾部。 如果有特殊需要,那么应该使用列表的insert()方法操作 。 上面的例子里,是在交互模式下修改sys.path的,在脚本程序中也完全可以达到同样的目的。
       可能一个模块 很多拷贝。 这时,解释器会使用沿搜索路径顺序找到的第一个模块。使用sys.modules可以找到当前导入了哪些模块和它们来自什么地方。和sys.path不同,sys.modules是一个字典,使用模块名作为键(key),对应物理地址作为值(value)。

2、名称空间

        名称空间是名称(标识符)到对象的映射。 向名称空间添加名称的操作过程涉及到绑定标识符到指定对象的操作(以及给该对象的引用计数加 1 )。 改变一个名字的绑定叫做重新绑定,删除一个名字叫做解除绑定。
        函数在执行期间有两个或三个活动的名称空间, 这三个名称空间分别是局部名称空间、全局名称空间和内建名称空间,但局部名称空间在执行期间是不断变化的。 从名称空间中访问这些名字依赖于它们的加载顺序或是系统加载这些名称空间的顺序。
       Python解释器首先加载内建名称空间。它由__builtins__模块中的名字构成, 随后加载执行模块的全局名称空间,它会在模块开始执行后变为活动名称空间。 这样我们就有了两个活动的名称空间。如果在执行期间调用了一个函数,那么将创建出第三个名称空间即局部名称空间。 可以通过globals()和locals()内建函数判断出某一名字属于哪个名称空间。
        __builtins__模块和__builtin__模块不能混淆。 虽然它们的名字相似。但__builtins__模块包含内建名称空间中内建名字的集合。 其中大多数(如果不是全部的话)来自__builtin__模块,该模块包含内建函数、异常以及其他属性。 在标准Python执行环境下,__builtins__包含__builtin__的所有名字。 Python曾经有一个限制执行模式,允许你修改__builtins__,只保留来自__builtin__的一部分,创建一个沙盒(sandbox)环境。但是因为它有一定的安全缺陷,而且修复它很困难,所以Python已经不再支持限制执行模式。
(1)名称空间与变量作用域比较
       名称空间是纯粹意义上的名字和对象间的映射关系, 而作用域还指出了从用户代码的哪些物理位置可以访问到这些名字。
Python编程基础之十一模块
       每个名称空间是一个自我包含的单元。但从作用域的观点来看, 事情是不同的。所有局部名称空间的名称都在局部作用范围内。局部作用范围以外的所有名称都在全局作用范围内。还要记得在程序执行过程中, 局部名称空间和作用域会随函数调用而不断变化,而全局名称空间是不变的。
(2)名称查找、确定作用域、覆盖
        那么确定作用域的规则是如何联系到名称空间的呢? 它所要做的就是名称查询。访问一个属性时,解释器必须在三个名称空间中的一个找到它。 首先从局部名称空间开始;如果没有找到解释器将继续查找全局名称空间;如果这也失败了,它将在内建名称空间里查找; 如果最后的尝试也失败了,则报出NameError异常。
        局部名称空间中找到的名字会隐藏全局或内建名称空间的对应对象,这就相当于"覆盖"了那个全局变量。
(3)无限制的名称空间
        Python的一个有用的特性在于可以在任何需要放置数据的地方获得一个名称空间。 即在任何时候给函数添加属性(使用熟悉的句点属性标识)。
>>> def foo():
... pass
...
>>> foo.__doc__ = 'Oops, forgot to add doc str above!'
>>> foo.version = 0.2
>>> print foo.__doc__
Oops, forgot to add doc str above!
>>> print foo.version
0.2
       可以把任何想要的东西放入一个名称空间里,像这样使用一个类(实例)是很好的,甚至不需要知道一些关于OOP的知识。不管名字如何,这个实例只是被用做一个名称空间。

3、导入模块

(1)import语句

       使用 import 语句导入模块, 它的语法如下所示:
import module1
import module2
:
import moduleN
       也可以在一行内导入多个模块,如:import module1[, module2[,... moduleN]]
       import 语句的模块推荐在Python模块的开头部分导入,导入顺序为:Python标准库模块、Python第三方模块、应用程序自定义模块。如果在一个模块的顶层导入,那么它的作用域就是全局的;如果在函数中导入, 那么它的作用域是局部的。如果模块是被第一次导入,它将被加载并执行。
(2)from-import 语句
        可以在模块中导入指定的模块属性,也就是把指定名称导入到当前作用域。 使用from-import语句可以实现,它的语法是:from module import name1[, name2[,... nameN]]。
(3)多行导入
       多行导入特性是Python 2.4为较长的from-import提供的。从一个模块导入许多属性时,import行会越来越长,直到自动换行, 而且需要一个\(可以使用多行的from-import语句替代)。
        不提倡使用不再流行的from Tkinter import * 语句,它不是良好的编程风格,因为它"污染"当前名称空间,而且很可能覆盖当前名称空间中现有的名字,真正的Python程序员应该使用Python的标准分组机制(圆括号)来创建更合理的多行导入语句。只建议在以下场合下使用:一、目标模块中的属性非常多,反复键入模块名很不方便;二、在交互解释器下, 因为这样可以减少输入次数

(4)扩展的 import 语句(as)
        有时候导入的模块或是模块属性名称已经在程序中使用了,或者可能是它太长不便输入什么的。 反正是想用自己的名字替换模块的原始名称。一个普遍的解决方案是把模块赋值给一个变量,但不太好用,而扩展的import 可以在导入的同时指定局部绑定名称,解决了这个问题。如from cgi import FieldStorage as form。

4、模块导入的特性

(1)载入时执行模块
        加载模块会导致这个模块被"执行", 也就是被导入模块的顶层代码将直接被执行。 这通常包括设定全局变量以及类和函数的声明。 如果有检查 __name__ 的操作,那么根据判断执行。把函数和模块定义放入模块的顶层是良好的模块编程习惯。Python加入了一个新特性允许把一个已经安装的模块作为脚本执行。
(2)导入(import )和加载(load)
       一个模块只被加载一次,无论它被导入多少次。 这可以阻止多重导入时代码被多次执行。 例如你的模块导入了 sys 模块, 而你要导入的其他5个模块也导入了它,那么每次都加载不是明智之举! 所以加载只在第一次导入时发生。
(3)导入到当前名称空间的名称
       调用from-import可以把名字导入当前的名称空间里去,这意味着你不需要使用属性/句点属性标识来访问模块的标识符。 例如,需要访问模块module中的var名字是这样被导入的:from module import var,可以使用单个的var就可以访问它自身。 把var导入到名称空间后就再没必要引用模块了。
(4)被导入到导入者作用域的名字
        只从模块导入名字的另一个副作用是那些名字会成为局部名称空间的一部分。 这可能导致覆盖一个已经存在的具有相同名字的对象。 而且对这些变量的改变只影响它的局部拷贝而不是所导入模块的原始名称空间。 也就是说, 绑定只是局部的而不是整个名称空间。
        唯一的解决办法是使用import和完整的标识符名称(句点属性标识)。
(5)__future__
        自Python2.0后由于改进、新特性以及当前特性增强,某些变化会影响到当前功能。 所以为了让Python程序员为新事物做好准备,Python实现了__future__指令。使用from-import语句"导入"新特性, 用户可以尝试一下新特性或特性变化,以便在特性固定下来的时候修改程序。 它的语法是:from __future__ import new_feature,import __future__不会有任何变化,所以这是被禁止的。 (事实上这是允许的,但它不会如你所想的那样启用所有特性) 你必须显示地导入指定特性。
(6)警告框架
        首先是应用程序员接口API, 程序员应该有从Python程序(通过调用warnings模块)或是C中(通过PyErr_Warn()调用)发布警告的能力。这个框架的另个部分是一些警告异常类的集合。Warning直接从Exception继承,作为所有警告的基类: UserWarning、DeprecationWarning、SyntaxWarning以及RuntimeWarning。另一个组件是警告过滤器,由于过滤有多种级别和严重性, 所以警告的数量和类型应该是可控制的。 警告过滤器不仅仅收集关于警告的信息(例如行号, 警告原因等等),而且还控制是否忽略警告,是否显示——自定义的格式——或者转换为错误(生成一个异常)。警告会有一个默认的输出显示到sys.stderr,不过有钩子可以改变这个行为。Python还提供了一个可以操作警告过滤器的API,最后,命令行也可以控制警告过滤器。 可以在启动Python解释器的时候使用 -W选项。
(7)从ZIP文件中导入模块
        在2.3版中,Python加入了从ZIP归档文件导入模块的功能。 如果你的搜索路径中存在一个包含Python模块(.py, .pyc, or .pyo 文件)的 .zip 文件,导入时会把 ZIP 文件当作目录处理,在文件中搜索模块。如果要导入的一个ZIP文件只包含.py文件,那么Python不会为其添加对应的.pyc文件,这意味着如果一个ZIP归档没有匹配的.pyc文件时,导入速度会相对慢一点。同时也可以为.zip文件加入特定的(子)目录,例如/tmp/yolk.zip/lib只会从yolk归档的 lib/ 子目录下导入。
(8)"新的"导入钩子
         导入ZIP归档文件这一特性其实新导入钩子(import hook),因为在这之前实现自定义导入器只能是使用一些很古老的模块, 它们并不会简化创建导入器。 另一个解决方法是覆盖__import__(),但这并不简单,需要(重新)实现整个导入机制。
        Python 2.3引入的新导入钩子,从而简化了这个操作。 只需要编写可调用的import类,然后通过sys模块"注册"(或者叫"安装")。它。你需要两个类:一个查找器和一个载入器。 这些类的实例接受一个参数:模块或包的全名称。查找器实例负责查找你的模块, 如果它找到,那么它将返回一个载入器对象。查找器可以接受一个路径用以查找子包(subpackages) 。载入器会把模块载入到内存,它负责完成创建一个 Python模块所需要的一切操作,然后返回模块。
       这些实例被加入到sys.path_hooks。sys.path_importer_cache只是用来保存这些实例, 这样就只需要访问path_hooks一次。 最后sys.meta_path用来保存一列需要在查询sys.path之前访问的实例,这些是为那些已经知道位置而不需要查找的模块准备的。meta-path已经有了指定模块或包的载入器对象的读取器。

5、模块内建函数

(1)__import__()
        提供这个函数是为了让有特殊需要的用户覆盖它,实现自定义的导入算法。__import__()的语法是:
__import__(module_name[, globals[, locals[, fromlist]]]),module_name变量是要导入模块的名称,globals是包含当前全局符号表的名字的字典,locals是包含局部符号表的名字的字典,fromlist是一个使用from-import语句所导入符号的列表。globals、locals 以及fromlist参数都是可选的,默认分别为globals()、locals()和[] 。调用import sys语句可以使用语句:sys = __import__('sys')。
(2)globals()和locals()
        globals()和locals()内建函数分别返回调用者全局和局部名称空间的字典。在一个函数内部,局部名称空间代表在函数执行时候定义的所有名字,locals()函数返回的就是包含这些名字的字典,globals()会返回函数可访问的全局名字。在全局名称空间下, globals()和locals()返回相同的字典,因为这时的局部名称空间就是全局空间。
>>> def foo():
... print 'calling foo()...'
... aString = 'bar'
... anInt = 42
... print "foo()'s globals:", globals().keys()
... print "foo()'s locals:", locals().keys()
...
>>> print "__main__'s globals:", globals().keys()
__main__'s globals: ['__builtins__', '__name__', 'foo', '__doc__', '__package__']
>>> print "__main__'s locals:", locals().keys()
__main__'s locals: ['__builtins__', '__name__', 'foo', '__doc__', '__package__']
>>> foo()
calling foo()...
foo()'s globals: ['__builtins__', '__name__', 'foo', '__doc__', '__package__']
foo()'s locals: ['anInt', 'aString']
(3)reload()
        reload()内建函数可以重新导入一个已经导入的模块。 它的语法如下:reload(module)。module是想要重新导入的模块,使用 reload()的时候有一些标准。首先模块必须是全部导入(不是使用 from-import), 而且它必须被成功导入。另外reload()函数的参数必须是模块自身而不是包含模块名的字符串。 也就是说必须类似reload(sys)而不是reload('sys')。模块中的代码在导入时被执行, 但只执行一次。以后执行import语句不会再次执行这些代码,只是绑定模块名称, 而 reload() 函数不同。

6、

       包是一个有层次的文件目录结构,它定义了一个由模块和子包组成的Python应用程序执行环境。用来帮助解决如下问题:一、为平坦的名称空间加入有层次的组织结构;二、允许程序员把有联系的模块组合到一起;三、允许分发者使用目录结构而不是一大堆混乱的文件;四、帮助解决有冲突的模块名称。
        与类和模块相同,包也使用句点属性标识来访问他们的元素。使用标准的import和from-import语句导入包中的模块。
       若Phone是最顶层的包, Voicedta等是它的子包。 可以这样导入子包:
import Phone.Mobile.Analog
Phone.Mobile.Analog.dial()
也可使用 from-import 实现不同需求的导入,然后使用属性/点操作符向下引用子包树:
from Phone import Mobile
Mobile.Analog.dial('555-1212')
       在目录结构中,可以发现很多的__init__.py文件。 这些是初始化模块,from-import语句导入子包时需要用到它。 如果没有用到, 它们可以是空文件。 程序员经常忘记为它们的包目录加入__init__.py文 件,所以从Python 2.5开 始,这将会导致一个ImportWarning信息。
        包同样支持from-import all语句:from package.module import *,然而这样的语句会导入哪些文件取决于操作系统的文件系统。所以在__init__.py中加入__all__变量。该变量包含执行这样的语句时应该导入的模块的名字,它由一个模块名字符串列表组成。
        绝对导入:在很多情况下导入子包会导致和真正的标准库模块发生(事实上是它们的名字)冲突。 包模块会把名字相同的标准库模块隐藏掉,因为它首先在包内执行相对导入,隐藏掉标准库模块。为此所有的导入现在都被认为是绝对的,即这些名字必须通过Python路径(sys.path 或是 PYTHONPATH )来访问。从Python 2.7开始, 绝对导入特性将成为默认功能。
        相对导入:绝对导入特性限制了模块作者的一些特权,失去了import语句的*,必须有新的特性来满足程序员的需求。这时候,有了相对导入。 相对导入特性稍微地改变了import语法,让程序员告诉导入者在子包的哪里查找某个模块,因为import语句总是绝对导入的,所以相对导入只应用于from-import语句。语法的第一部分是一个句点,指示一个相对的导入操作, 之后的其他附加句点代表当前 from起始查找位置后的一个级别。从2.5 版开始,相对导入被加入到了Python中 。在 Python 2.6中,在模块内部的导入如果没有使用相对导入, 那么会显示一个警告信息。
from Phone.Mobile.Analog import dial
from .Analog import dial
from ..common_util import setup
from ..Fax import G3.dial.

7、模块的其他特性

(1)自动载入的模块
        当Python解释器在标准模式下启动时,一些模块会被解释器自动导入,用于系统相关操作。唯一一个影响的是__builtin__模块,它会正常地被载入, 这和__builtins__模块相同。sys.modules变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典,模块名作为键,它们的位置作为值。
       sys.modules变量包含大量载入的模块,linux下通过调用字典的keys()方法输出模块名:
>>> import sys
>>> sys.modules.keys()
['copy_reg', 'encodings', 'site', '__builtin__', '__main__', 'encodings.encodings', 'repoze.who', \
'repoze.who.plugins', 'abc', 'posixpath', 'repoze', 'errno', 'encodings.codecs', '_abcoll', \
'repoze.what.plugins', 'types', '_codecs', '_warnings', 'genericpath', 'stat', 'zipimport', \
'encodings.__builtin__', 'warnings', 'UserDict', 'encodings.utf_8', 'sys', 'peak.util', 'peak', \
 'codecs', 'readline', 'paste', 'os.path', 'repoze.what', 'signal', 'tw.mods', 'linecache', \
'posix', 'encodings.aliases', 'tw', 'exceptions', 'abrt_exception_handler', 'os']
(2)阻止属性导入
        如果你不想让某个模块属性被 "from module import *" 导入,那么你可以给不想导入的属性名称加上一个下划线( _ )。 不过如果你导入了整个模块或是你显式地导入某个属性(例如 import foo._bar ),这个隐藏数据的方法就不起作用了。
(3)不区分大小的导入
        有一些操作系统的文件系统是不区分大小写的,为了让不区分大小写的导入正常工作,必须指定一个叫做PYTHONCASEOK的环境变量。 Python会导入第一个匹配模块名( 使用不区分大小写的习惯 )。否则Python会执行它的原生区分大小写的模块名称匹配,导入第一个匹配的模块。
(4)源代码编码
         从Python 2.3开始, Python的模块文件开始支持除7位ASCII之外的其他编码。 当然ASCII是默认的,只要在Python模块头部加入一个额外的编码指示说明就可以让导入者使用指定的编码解析你的模块, 编码对应的Unicode字符串。 所以使用纯ASCII文本编辑器的时候不需要担心了(不需要把你的字符串放入 "Unicode 标签" 里) 。如果你执行或导入了包含非 ASCII 的Unicode字符串而没有在文件头部说明,那么Python 2.3得到一个DeprecationWarning,而在2.5中这样做会导致语法错误。
一个UTF-8编码的文件可以这样指示:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-     或#coding=utf-8   或#coding=gbk
         如果你执行或导入了包含非 ASCII 的Unicode字符串而没有在文件头部说明,那么Python 2.3得到一个DeprecationWarning,而在2.5中这样做会导致语法错误。
(5)导入循环
        在使用Python时,会发现是能够导入循环的,一个模块导入另一个模块,而另一个模块又需要导入一个模块。 如果开发了大型的Python工程,那么很可能会陷入这样的境地。
        如omh4cli尝试导入cli4vof,而cli4vof也试着导入omh4cli。最后谁也不会完成导入工作,引发错误。 这只是一个导入循环的例子,事实上实际应用中会出现更复杂的情况。解决这个问题几乎总是移除其中一个导入语句,你经常会在模块的最后看到 import 语句,作为一个初学者,你只需要试着习惯它们, 如果你以前遇到在模块底部的 import 语句,现在你知道是为什么了。

        但有时import omh4cli不能移到做后,因为调用cli4vof()时,该模块名字omh4cli还没有被载入,解决方法只是把import语句移到cli4vof() 函数内部:

def cli4vof():
    import omh4cli
    omh4cli.cli_util()
        这样从omh4cli()导入cli4vof()模块会顺利完成,在omh4cli()被调用之前它会被正确导入,只有在执行到cli4vof.cli4vof()时候才会导入omh4cli模块。
(6)模块执行
       有很多方法可以执行一个Python模块:通过命令行或shell、execfile()、模块导入、解释器的 -m 选项等等。

8、相关模块

下边这些模块可能是在处理Python模块导入时会用到的辅助模块:
Python编程基础之十一模块

三、总结

(1)模块是Pyhon*别的程序组织单元,它将程序代码和数据封装起来以便重用,模块往往对应Python程序文件,每个文件都是一个模块,并且模块导入其他模块之后就可以使用导入模块定义的变量名。
(2)Python具有强大的扩展能力,有大量的Python模块可供使用,在实际的开发中可查看相应文档。
(3)若有不足,请留言,在此先感谢!