12 模块 - 《Python 核心编程》

􀁺 什么是模块?
􀁺 模块和文件
􀁺 命名空间
􀁺 导入模块
􀁺 导入模块属性
􀁺 模块内建函数包
􀁺 模块的其他特性
12.1 什么是模块
    模块是用来组织 Python 代码的方法, 而包则是用来组织模块的。
    模块支持从逻辑上组织 Python 代码。
    把其他模块中属性附加到你的模块中的操作叫做导入(import) 。
    那些自我包含并且有组织的代码片断就是模块( module )。
        代码片段, 可能是一个包含数据成员和方法的类, 也可能是一组相关但彼此独立的操作函数。
12.2 模块和文件
    模块是按照逻辑来组织 Python 代码的方法, 文件是物理层上组织模块的方法。
    一个文件被看作是一个独立模块,一个模块也可以被看作是一个文件。 
    模块的文件名就是模块的名字加上扩展名 .py 。
    与其它可以导入类 (class)的语言不同,在 Python 中你导入的是模块或模块属性。
模块名称空间
    从基本概念来说, 一个名称空间就是一个从名称到对象的关系映射集合。
    给定一个模块名之后, 只可能有一个模块被导入到 Python 解释器中。
    在不同模块间不会出现名称交叉现象。
    每个模块都定义了它自己的唯一的名称空间。
    完整授权名称(fully qualified name)
        通过句点属性标识指定了各自的名称空间 - 防止了名称冲突(属性之间名称冲突)的发生。
    例如 string 模块中的 atoi() 函数就是 string.atoi() 。
搜索路径和路径搜索
    模块的导入需要一个叫做"路径搜索"的过程。
        即在文件系统"预定义区域"中查找*.py 文件。
        这些预定义区域只是你的 Python 搜索路径的集合。
            有时候导入模块操作会失败: 
            >>> import xxx
            Traceback (innermost last):
            File "<interactive input>", line 1, in ?
            ImportError: No module named xxx
            发生这样的错误时, 解释器会告诉你它无法访问请求的模块, 可能的原因是模块不在搜索路 径里, 从而导致了路径搜索的失败。
    默认搜索路径是在编译或是安装时指定的。
        一个是启动 Python 的 shell 或命令行的 PYTHONPATH 环境变量。 该变量的内容是一组用冒号分割的目录路径。 如果你想让解释器使用这个变量, 那么请确保在启动解释器或执行 Python 脚本前设置或修改了该变量。
        解释器启动之后, 也可以访问这个搜索路径, 它会被保存在 sys 模块的 sys.path 变量里。不过它已经不是冒号分割的字符串, 而是包含每个独立路径的列表。切记, 搜索路径在不同系统下一般是不同的。 sys.path 变量只是个列表, 所以我们可以随时随地对它进行修改。修改完成后, 你就可以加载自己的模块了。
        使用 sys.modules 可以找到当前导入了哪些模块和它们来自什么地方。 和 sys.path 不同, sys.modules 是一个字典, 使用模块名作为键( key) , 对应物理地址作为值( value )。
12.3 名称空间
    名称空间是名称(标识符)到对象的映射。
        向名称空间添加名称的操作过程涉及到绑定标识符到指定对象的操作(以及给该对象的引用计数加 1 )。
        改变一个名字的绑定叫做重新绑定,
        删除一个名字叫做解除绑定。
    Python 解释器在执行期间有两个或三个活动的名称空间。
        三个名称空间分别是局部名称空间, 全局名称空间和内建名称空间, 局部名称空间在执行期间是不断变化的, 所以我们说"两个或三个"。
        从名称空间中访问这些名字依赖于它们的加载顺序, 或是系统加载这些名称空间的顺序。
            Python 解释器首先加载内建名称空间。 它由 __builtins__ 模块中的名字构成。 
            随后加载执行模块的全局名称空间, 它会在模块开始执行后变为活动名称空间。 这样我们就有了两个活动的名称空间。 
            如果在执行期间调用了一个函数, 那么将创建出第三个名称空间, 即局部名称空间。

            通过 globals() 和 locals() 内建函数判断出某一名字属于哪个名称空间。

    核心笔记: __builtins__ 和 __builtin__
        __builtins__ 模块和 __builtin__ 模块不能混淆。 虽然它们的名字相似——尤其对于新手来
    说。 __builtins__ 模块包含内建名称空间中内建名字的集合。 其中大多数(如果不是全部的话)来
    自 __builtin__ 模块, 该模块包含内建函数, 异常以及其他属性。 在标准 Python 执行环境下,
    __builtins__ 包含 __builtin__ 的所有名字。 Python 曾经有一个限制执行模式, 允许你修改
    __builtins__ , 只保留来自 __builtin__ 的一部分, 创建一个沙盒(sandbox)环境。但是, 因为
    它有一定的安全缺陷, 而且修复它很困难, Python 已经不再支持限制执行模式。(如版本2.3 )
名称空间与变量作用域比较
    名称空间是纯粹意义上的名字和对象间的映射关系, 而作用域还指出了从用户代码的哪些物理位置可以访问到这些名字。
    还要记得在程序执行过程中, 局部名称空间和作用域会随函数调用而不断变化, 而全局名称空间是不变的。
     
        图 12 - 1 名称空间和变量作用域
名称查找, 确定作用域, 覆盖
    名称查询
    访问一个属性时, 解释器必须在三个名称空间中的一个找到它。 
        首先从局部名称空间开始,
        如果没有找到, 解释器将继续查找全局名称空间. 
        如果这也失败了, 它将在内建名称空间里查找。
        如果最后的尝试也失败了, 你会得到这样的错误:
            >>> foo
            Traceback (most recent call last):
              File "<stdin>", line 1, in <module>
            NameError: name 'foo' is not defined
            >>>
    覆盖
        局部名称空间中找到的名字会隐藏全局或内建名称空间的对应对象。
            >>> foo = 'global foo'
            >>> def bar():
            ... foo = 'local foo'
            ... print(foo)
            ...
            >>> print(foo)
            global foo
            >>> bar()
            local foo
            >>>
无限制的名称空间
    Python 的一个有用的特性在于你可以在任何需要放置数据的地方获得一个名称空间。(句点属性标识)
        >>> def foo():
        ... pass
        ...
        >>> foo.__doc__ = 'Oops, forgot to add doc str above!'
        >>> foo.version = 0.2
        >>> class MyUltimatePythonStorageDevice(object):
        ... pass
        ...
        >>> bag = MyUltimatePythonStorageDevice()
        >>> bag.x = 100
        >>> bag.y = 200
        >>> bag.version = 0.1
        >>> bag.completed = False
        >>>
12.4 导入模块
import 语句
    使用 import 语句导入模块
        import module1[, module2[,... moduleN]]
    import 语句的模块顺序
        我们推荐所有的模块在 Python 模块的开头部分导入。 而且最好按照这样的顺序:
        􀁺 Python 标准库模块
        􀁺 Python 第三方模块
        􀁺 应用程序自定义模块
        然后使用一个空行分割这三类模块的导入语句。 这将确保模块使用固定的习惯导入, 有助于减少每个模块需要的 import 语句数目。 
        解释器执行到这条语句, 如果在搜索路径中找到了指定的模块, 就会加载它
        该过程遵循作用域原则, 如果在一个模块的顶层导入, 那么它的作用域就是全局的; 如果在函数中导入, 那么它的作用域是局部的。 
        如果模块是被第一次导入, 它将被加载并执行
from-import 语句
    可以在你的模块里导入指定的模块属性,也就是把指定名称导入到当前作用域。
        from module import name1[, name2[,... nameN]]
            >>> from os import system as syst
            >>> syst('cls')
            ... ...
            0
            >>>
多行导入
    多行导入特性是 Python 2.4 为较长的 from-import 提供的。
    从一个模块导入许多属性时, import 行会越来越长, 直到自动换行, 而且需要一个 \ 。
    样例代码:
        from Tkinter import Tk, Frame, Button, Entry, Canvas, \
        Text, LEFT, DISABLED, NORMAL, RIDGE, END
    使用多行的 from-import 语句:
        from Tkinter import Tk, Frame, Button, Entry, Canvas, Text
        from Tkinter import LEFT, DISABLED, NORMAL, RIDGE, END
    我们不提倡使用不再流行的 from Tkinter import * 语句 。
    真正的 Python 程序员应该使用 Python 的标准分组机制(圆括号)来创建更合理的多行导入语句:
扩展的 import 语句(as)
    使用自己想要的名字替换模块的原始名称。
        一个普遍的解决方案是把模块赋值给一个变量: 
            >>> import longmodulename
            >>> short = longmodulename
            >>> del longmodulename
        使用扩展的 import , 你就可以在导入的同时指定局部绑定名称。
            import Tkinter
            from cgi import FieldStorage
            . . . 可以替换为 . . .
            import Tkinter as tk
            from cgi import FieldStorage as form
12.5 模块导入的特性
载入时执行模块
    加载模块会导致这个模块被"执行"。 也就是被导入模块的顶层代码将直接被执行。 这通常包括设定全局变量以及类和函数的声明。 
    如果有检查 __name__ 的操作, 那么它也会被执行。当然, 这样的执行可能不是我们想要的结果。 
    你应该把尽可能多的代码封装到函数,只把函数和模块定义放入模块的顶层是良好的模块编程习惯
导入(import )和加载(load)
    一个模块只被加载一次, 无论它被导入多少次。
    这可以阻止多重导入时代码被多次执行。
    加载只在第一次导入时发生
导入到当前名称空间的名称
    调用 from-import 可以把名字导入当前的名称空间里去, 这意味着你不需要使用属性/句点属性标识来访问模块的标识符。
        >>> from sys import path
        >>> path
            ['', 'C:\\Windows\\system32\\python33.zip', 'C:\\Python33\\DLLs', 'C:\\Python33\ \lib', 'C:\\Python33', 'C:\\Python33\\lib\\site-packages'] 
        >>>
    把指定模块的所有名称导入到当前名称空间里:
        from module import *
    核心风格: 限制使用 "from module import *"
        在实践中, 我们认为 "from module import *" 不是良好的编程风格, 因为它"污染"当前名称
    空间, 而且很可能覆盖当前名称空间中现有的名字; 但如果某个模块有很多要经常访问的变量或者
    模块的名字很长, 这也不失为一个方便的好办法。
        我们只在两种场合下建议使用这样的方法, 一个场合是:目标模块中的属性非常多, 反复键入
    模块名很不方便, 例如 Tkinter (Python/Tk) 和 NumPy (Numeric Python) 模块, 可能还有
    socket 模块。另一个场合是在交互解释器下, 因为这样可以减少输入次数。
被导入到导入者作用域的名字
    只从模块导入名字的另一个副作用是那些名字会成为局部名称空间的一部分。 
        这可能导致覆盖一个已经存在的具有相同名字的对象。 而且对这些变量的改变只影响它的局部拷贝而不是所导入模块的原始名称空间。 也就是说, 绑定只是局部的而不是整个名称空间。
        唯一的解决办法是使用 import 和完整的标识符名称(句点属性标识)。
关于 __future__
	回首 Python 2.0 , 我们认识到了由于改进, 新特性, 以及当前特性增强, 某些变化会影响到
当前功能。 所以为了让 Python 程序员为新事物做好准备, Python 实现了 __future__ 指令。
	使用 from-import 语句"导入"新特性, 用户可以尝试一下新特性或特性变化, 以便在特性固
定下来的时候修改程序。 它的语法是:
from __future__ import new_feature
	只 import __future__ 不会有任何变化,所以这是被禁止的。 (事实上这是允许的, 但它不会
如你所想的那样启用所有特性。) 你必须显示地导入指定特性。 你可以在 PEP 236 找到更多关于
__future__ 的资料。
警告框架
	和 __future__ 指令类似, 有必要去警告用户不要使用一个即将改变或不支持的操作, 这样他
们会在新功能正式发布前采取必要措施。 这个特性是很值得讨论的, 我们这里分步讲解一下。
	首先是应用程序(员)接口(Application programmers' interface , API)。 程序员应该有从
Python 程序(通过调用 warnings 模块)或是 C 中(通过 PyErr_Warn() 调用)发布警告的能力。
这个框架的另个部分是一些警告异常类的集合。 Warning 直接从 Exception 继承, 作为所有
警告的基类: UserWarning , DeprecationWarning , SyntaxWarning , 以及 RuntimeWarning 。 
	另一个组件是警告过滤器, 由于过滤有多种级别和严重性, 所以警告的数量和类型应该是可控
制的。 警告过滤器不仅仅收集关于警告的信息(例如行号, 警告原因等等), 而且还控制是否忽略警
告, 是否显示——自定义的格式——或者转换为错误(生成一个异常)。
	警告会有一个默认的输出显示到 sys.stderr , 不过有钩子可以改变这个行为, 例如,当运行
会引发警告的 Python 脚本时,可以记录它的输出记录到日志文件中,而不是直接显示给终端用户。
Python 还提供了一个可以操作警告过滤器的 API 。
	最后, 命令行也可以控制警告过滤器。 你可以在启动 Python 解释器的时候使用 -W 选项。请
参阅 PEP 230 的文档获得你的 Python 版本的对应开关选项。 Python 2.1 第一次引入警告框架。
从 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/ 子目录下导入。 虽然 PEP 273 指定了这个特性, 但事实上使用了 PEP 302 提供的导入
钩子来实现它。
"新的"导入钩子
	导入 ZIP 归档文件这一特性其实新导入钩子( import hook , PEP 302) 的 "第一个顾客"。我
们使用了"新"这个字, 因为在这之前实现自定义导入器只能是使用一些很古老的模块, 它们并不会
简化创建导入器。 另一个解决方法是覆盖 __import__() , 但这并不简单, 你需要(重新)实现整个
导入机制。
	Python 2.3 引入的新导入钩子,从而简化了这个操作。 你只需要编写可调用的 import 类, 然
后通过 sys 模块"注册"(或者叫"安装")它。
	你需要两个类: 一个查找器和一个载入器。 这些类的实例接受一个参数:模块或包的全名称。
查找器实例负责查找你的模块, 如果它找到, 那么它将返回一个载入器对象。查找器可以接受一个
路径用以查找子包(subpackages) 。载入器会把模块载入到内存。它负责完成创建一个 Python 模
块所需要的一切操作, 然后返回模块。
	这些实例被加入到 sys.path_hooks 。 sys.path_importer_cache 只是用来保存这些实例, 这
样就只需要访问 path_hooks 一次。 最后, sys.meta_path 用来保存一列需要在查询 sys.path 之
前访问的实例, 这些是为那些已经知道位置而不需要查找的模块准备的。 meta-path 已经有了指
定模块或包的载入器对象的读取器。
12.6 模块内建函数
    系统还为模块提供了一些功能上的支持.
__import__()
    Python 1.5 加入了 __import__() 函数, 它作为实际上导入模块的函数, 这意味着 import 语句调用 __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')
globals() 和 locals()
globals() 和 locals() 内建函数分别返回调用者全局和局部名称空间的字典。 
    在一个函数内部, 局部名称空间代表在函数执行时候定义的所有名字,
        locals() 函数返回的就是包含这些名字的字典
        globals() 会返回函数可访问的全局名字
    在全局名称空间下, globals() 和 locals() 返回相同的字典, 因为这时的局部名称空间就是全局空间。
reload()
    reload() 内建函数可以重新导入一个已经导入的模块。 它的语法如下:
        reload(module)
        module 是你想要重新导入的模块。
    使用 reload() 的时候有一些标准。
        首先模块必须是全部导入(不是使用 from-import), 而且它必须被成功导入。
        另外 reload() 函数的参数必须是模块自身而不是包含模块名的字符串,也就是说必须类似 reload(sys) 而不是 reload('sys')。 
        模块中的代码在导入时被执行, 只执行一次. 以后执行 import 语句不会再次执行这些代码, 只是绑定模块名称。而 reload() 函数不同。
12.7 包
    包是一个有层次的文件目录结构, 它定义了一个由模块和子包组成的 Python 应用程序执行 环境。
    Python 1.5 加入了包, 用来帮助解决如下问题: 
        􀁺 为平坦的名称空间加入有层次的组织结构
        􀁺 允许程序员把有联系的模块组合到一起
        􀁺 允许分发者使用目录结构而不是一大堆混乱的文件
        􀁺 帮助解决有冲突的模块名称
    与类和模块相同, 包也使用句点属性标识来访问他们的元素。 
    使用标准的 import 和 from-import 语句导入包中的模块。
目录结构
       
                                       
    导入子包:
        import Phone.Mobile.Analog
        Phone.Mobile.Analog.dial()
    使用 from-import 实现不同需求的导入
        导入顶层的子包, 然后使用属性/点操作符向下引用子包树:
            from Phone import Mobile
            Mobile.Analog.dial('555-1212')
        此外, 我们可以还引用更多的子包:
            from Phone.Mobile import Analog
            Analog.dial('555-1212')
        一直沿子包的树状结构导入:
            from Phone.Mobile.Analog import dial
            dial('555-1212')
    __init__.py 文件
        目录结构中的 __init__.py 文件是初始化模块,from-import 语句导入子包时需要用到的初始化模块,如果没有用到, 他们可以是空文件。
使用 from-import 导入包
    包同样支持 from-import all 语句:
        from package.module import *
        然而, 这样的语句会导入哪些文件取决于操作系统的文件系统. 
        所以我们在__init__.py 中加 入 __all__ 变量. 
        该变量包含执行这样的语句时应该导入的模块的名字. 
        它由一个模块名字符串列表组成.。
绝对导入
    包的使用越来越广泛, 很多情况下导入子包会导致和真正的标准库模块发生(事实上是它们的名字)冲突。 
    包模块会把名字相同的标准库模块隐藏掉, 因为它首先在包内执行相对导入, 隐藏掉标准库模块。 
    所有的导入现在都被认为是绝对的, 也就是说这些名字必须通过 Python 路径 (sys.path 或是 PYTHONPATH )来访问。
    这个决定的基本原理是子包也可以通过 sys.path 访问。
    python 允许通过在模块或包名称前置句点实现相对导入。
    从 python 2.7 开始, 绝对导入特性将成为默认功能。
相对导入
    import 语句总是绝对导入的, 
    相对导入只应用于 from-import 语句。
    语法的第一部分是一个句点, 指示一个相对的导入操作。 之后的其他附加句点代表当前 from 起始查找位置后的一个级别。
12.8 模块的其他特性
自动载入的模块
    当 Python 解释器在标准模式下启动时, 一些模块会被解释器自动导入, 用于系统相关操作。
    唯一一个影响你的是 __builtin__ 模块, 它会正常地被载入, 这和 __builtins__ 模块相同。
    sys.modules 变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典, 模块名作为键, 它们的位置作为值。
阻止属性导入
    如果你不想让某个模块属性被 "from module import *" 导入 , 那么你可以给你不想导入的属性名称加上一个下划线( _ )。 
    如果你导入了整个模块或是你显式地导入某个属性(例如 import foo._bar ), 这个隐藏数据的方法就不起作用了。
不区分大小的导入
    有一些操作系统的文件系统是不区分大小写的。
    在 Unix(区分大小写)和 Win32(不区分大小写)下, 一切都很明了, 但那些新的不区分大小写的系统不会被加入区分大小写的特性。
    底线就是为了让不区分大小写的导入正常工作, 必须指定一个叫做 PYTHONCASEOK 的环境变量。 
        python 会导入第一个匹配模块名( 使用不区分大小写的习惯 )。
        否则 python 会执行它的原生区分大小写的模块名称匹配, 导入第一个匹配的模块。
源代码编码
    从 Python 2.3 开始, Python 的模块文件开始支持除 7 位 ASCII 之外的其他编码。
    当然 ASCII 是默认的, 你只要在你的 Python 模块头部加入一个额外的编码指示说明就可以让导入者使用指定的编码解析你的模块, 编码对应的 Unicode 字符串。
	从 Python 2.3 开始, Python 的模块文件开始支持除 7 位 ASCII 之外的其他编码。 当然
ASCII 是默认的, 你只要在你的 Python 模块头部加入一个额外的编码指示说明就可以让导入者
使用指定的编码解析你的模块, 编码对应的 Unicode 字符串。 所以你使用纯 ASCII 文本编辑器的
时候不需要担心了(不需要把你的字符串放入 "Unicode 标签" 里) 。
	一个 UTF-8 编码的文件可以这样指示:
	#!/usr/bin/env python
	# -*- coding: UTF-8 -*-
	如果你执行或导入了包含非 ASCII 的 Unicode 字符串而没有在文件头部说明, 那么你会在
Python 2.3 得到一个 DeprecationWarning , 而在 2.5 中这样做会导致语法错误。你可以在 PEP
263 中得到更多关于源文件编码的相关内容。
导入循环
 
模块执行
    有很多方法可以执行一个 Python 模块: 通过命令行或 shell , execfile() , 模块导入, 解释器的 -m 选项, 等等。
12.9 相关模块
   
posted @ 2015-08-18 14:06  小麦粉  阅读(552)  评论(0编辑  收藏  举报