Python基础手册28——模块的高级概念

三、模块命名空间

命名空间(名称空间)中保存了变量名到对象的映射。向命名空间添加变量名的操作过程涉及到绑定变量到指定对象的操作(以及给该对象的引用计数加 1 )。改变一个变量的绑定叫做重新绑定,删除一个变量叫做解除绑定。

我们在之前已经介绍过在程序执行期间有两个或三个活动的命名空间。 这三个名称空间分别是局部命名空间,全局命名空间和内建命名空间,但局部命名空间在执行期间是不断变化的,所以我们说"两个或三个"。 从命名空间中访问这些变量名依赖于它们的加载顺序,或是系统加载这些命名空间的顺序。

Python 解释器首先加载内建命名空间。 它由 __builtins__ 模块中的名字构成。 随后加载执行文件的全局命名空间,它会在模块开始执行后变为活动命名空间。 这样我们就有了两个活动的名称空间。

如果在执行期间调用了一个函数,那么将创建出第三个命名空间,即局部命名空间。 我们可以通过 globals()locals() 内建函数判断出某一名字属于哪个命名空间。


1、命名空间和变量作用域

命名空间保存的是纯粹意义上的变量名和对象间的映射关系,而作用域还指出了从用户代码的哪些物理位置可以访问到这些变量名。

注意每个命名空间都是一个单独的单元。但从作用域的观点来看,事情是不同的。所有局部命名空间的名称都在局部作用范围内。局部作用范围以外的所有变量名都在全局作用范围内。

还要记得在程序执行过程中,局部命名空间和作用域会随函数调用而不断变化,而全局命名空间是不变的。


2、变量查找

那么作用域的规则是如何联系到命名空间的呢? 它所要做的就是变量名查询。访问一个变量时,解释器必须在三个命名空间中的一个找到它。 首先从局部命名空间开始,如果没有找到,解释器将继续查找全局命名空间。如果这也失败了,它将在内建命名空间里查找。

3、无限制的命名空间

Python 的一个有用的特性在于你可以在任何需要放置数据的地方获得一个命名空间。比如,你可以在任何时候给函数添加属性(使用熟悉的句点属性标识)。

4、文件生成命名空间

模块最好理解为变量名的封装,也就是定义想让系统其余部分看见变量名的场所。从技术上来讲,模块通常相应于文件,而 Python 会建立模块对象,以包含模块文件内所赋值的所有变量名。简而言之,模块就是命名空间(变量名建立所在的场所),而存在于模块之内的变量名就是模块对象的属性。

5、导入和作用域

不导入一个文件,就无法存取该文件内所定义的变量名。也就是说,你不能自动看见另一个文件内的变量名。对于函数也是一样的,函数绝对无法看见其他函数内的变量名,除非它们从物理上处于同一个函数内。模块程序代码绝对无法看见其他模块内的变量名,除非明确地进行了导入。

在 Python 中,一段程序的作用域完全由程序所处的文件中的实际位置决定。作用域绝不会被函数调用或模块导入影响。

解释器执行到这些导入语句,如果在搜索路径中找到了指定的模块,就会加载它。该过程遵循作用域原则,如果在一个模块的顶层导入,那么它的作用域就是全局的;如果在函数中导入,那么它的作用域是局部的。

6、命名空间的嵌套

虽然导入不会使命名空间发生向上的嵌套,但确实会发生向下的嵌套。利用属性的点号运算路径,有可能深入到任意嵌套的模块中并读取其属性。

\模块通过使用自包含的变量的包,也就是所谓的命名空间提供了将功能化的接口组织为系统的简单的方法。在一个模块文件的顶层定义的所有的变量名都成了被导入的模块对象的属性。导入给予了对模块的全局作用域中的变量的读取权。也就是说,在模块导入时,模块文件的全局作用域变成了模块对象的命名空间。Python 的模块允许将独立的文件连接成一个更大的程序系统。

四、模块搜索路径

通常来说,导入过程最重要的部分是最一开始的搜索部分,也就是定位要导入的文件。在大多数情况下,可以依赖模块导入搜索路径的自动特性,完全不需要配置这些路径。

Python 的模块搜索路径是下面这些部分组合而成的结果:
1、程序的主目录
2、PYTHONPATH 目录(如果已经进行了设置)
3、标准链接库目录
4、任何 .pth 文件的内容(如果存在的话)

这四个组件组合起来就变成了 sys.path 。默认搜索路径是在编译或是安装时指定的。搜索路径的第一和第三元素是自动定义的,第二和第四元素,可以用于拓展路径,从而包含你自己的源代码目录。

当导入的模块不在搜索路径里时,导入模块操作会失败。


1、主目录

Python 首先会在主目录内搜索导入的文件。当你运行一个程序的时候,这个目录就是你运行程序顶层脚本文件时所在的目录。当在交互模式下工作时,这一入口就是你当前的工作目录。

因为这个目录总是先被搜索,如果程序完全位于单一目录,所有导入都会自动工作,而不需要配置路径。另一方面,由于这个目录是先搜索的,其文件也将覆盖路径上的其他目录中具有同样名称的模块。


2、PYTHONPATH 目录

搜索完主目录之后如果没有发现相应的文件,Python会从左至右(假设你设置了的话)搜索 PYTHONPATH 环境变量设置中罗列出的所有目录。PYTHONPATH 是设置包含 Python 程序文件的目录的列表,这些目录可以是用户定义的或平台特定的目录名。你可以把想导入的目录都加进来,而 Python 会使用你的设置来扩展模块搜索的路径。

因为 Python 会先搜索主目录,当导入的文件跨目录时,这个设置才显得格外重要。也就是说,如果你需要被导入的文件与进行导入的文件处在不同目录时,可以考虑设置这个环境变量。


3、标准库目录

接着,Python 会自动搜索标准库模块的安装目录。因为这些一定会被搜索,通常是不需要添加到 PYTHONPATH 之中或包含到路径文件中的。

4、.pth 文件目录

Python 允许用户通过配置 .pth 文件把有效的目录添加到模块搜索路径中去,也就是在后缀名 .pth(路径的意思)的文本文件中一行一行地列出目录。

简而言之,当内含目录名称的文本文件放到适当目录时,也可以概括地扮演与PYTHONPATH 环境变量设置相同的角色。

当存在目录的时候,Python 会把文件每行所罗列的目录从头到尾加到模块搜索路径列表的最后。实际上,Python 会将收集它所找到的所有路径文件中的目录名,并且过滤掉任何重复的和不存在的目录。

搜索路径的 PYTHONPATH 和路径文件部分允许我们调整导入查找文件的地方。 搜索路径的配置可能随平台以及 Python 版本而异。取决于你所使用的平台,附加的目录也可能自动加入模块搜索路径。


5、sys.path 列表

如果你想查看模块搜索路径在机器上的实际配置,可以通过打印内置的 sys.path 列表(也就是标准库模块 sys 的 path 属性)来查看这个路径。目录名称的字符串列表就是Python 内部实际的搜索路径。导入时,Python 会由左至右搜索这个列表。

sys.path 是模块搜索的路径。Python 在程序启动时进行配置,自动将顶级文件的主目录(或者指定当前工作目录的一个空字符串)、任何 PYTHONPATH 目录、标准库目录,以及已经创建的任何 .pth 文件路径的内容合并。得到一个 Python 在每次导入一个新文件的时候查找的目录名的字符串列表。这个 sys.path 列表可以帮你确认你所做的搜索路径的设置值:如果在列表中看不到设置值,就需要重新检查你的设置。

如果你知道在做什么,这个列表也提供一种方式,让脚本手动调整其搜索路径。通过修改 sys.path 这个列表,你可以修改将来的导入的搜索路径。一旦做了这类修改,就会对 Python 程序中将要导入的地方产生影响,因为所有导入和文件都共享了同一个sys.path 列表。因此可以使用这个技巧,在 Python 程序中动态配置搜索路径。不过要小心:如果从路径中删除了重要目录,就无法获取一些关键的工具了。

sys.path 的设置方法只在修改的 Python 会话或程序(即进程)中才会存续。在Python 程序结束后,不会保留下来。PYTHONPATH 和 .pth 文件路径配置时保存在操作系统中,而不是执行的 Python 程序中。PYTHONPATH 和 .pth 文件提供了更改持久的路径修改方法。因此使用这种配置方法更全局一些:机器上的每个程序都会去查找PYTHONPATH 和 .pth,而且在程序结束后,他们还存在着。

修改完成后,你就可以加载自己的模块了。 只要这个列表中的某个目录包含这个文件, 它就会被正确导入。 当然, 这个方法是把目录追加在搜索路径的尾部。 如果你有特殊需要, 那么应该使用列表的 insert() 方法操作 ,把目录追加在搜索路径的首部。


6、模块文件选择

文件名的后缀(例如.py)是刻意从 import 语句中省略的。Python 会选择搜索路径中第一个符合导入文件名的文件。

  • 源代码文件:b.py
  • 字节码文件:b.pyc
  • 目录:b,包导入
  • 编译扩展模块(通常是C或C++编写),导入时使用动态连接
  • 用 C 编写的编译好的内置模块,并通过静态连接至 Python。
  • ZIP 文件组件,导入时会自动解压缩
  • 内存映像,对于 frozen 可执行文件。

如果在不同目录中有 b.py 和 b.so,Python 总是在由左至右搜索 sys.path 时,加载模块搜索路径那些目录中最先出现(最左边的)相符文件。但是,如果实在相同的目录中找到这两个文件,Python会遵循一个标准的调训顺序,不过这种顺序不保证永远保持不变,通常来说,你不应该依赖 Python 会在给定的目录中选择何种的文件类型:让模块名独特一些,或者设置模块搜索路径,让模块选择的特性更明确一些。


五、模块的高级概念

1、核心风格: 导入语句的位置

推荐所有的模块在 Python 模块的开头部分导入。 而且最好按照这样的顺序:

  • Python 标准库模块
  • Python 第三方模块
  • 应用程序自定义模块

然后使用一个空行分割这三类模块的导入语句。 这将确保模块使用固定的习惯导入,有助于减少每个模块需要的 import 语句数目。

2、从 ZIP 文件中导入模块

在 2.3 版中,Python 加入了从 ZIP 归档文件导入模块的功能。 如果你的搜索路径中存在一个包含 Python 模块(.py, .pyc, or .pyo 文件)的 .zip 文件,导入时会把 ZIP 文件当作目录处理,在文件中搜索模块。
如果要导入的一个 ZIP 文件只包含 .py 文件, 那么 Python 不会为其添加对应的 .pyc 文件,这意味着如果一个 ZIP 归档没有匹配的 .pyc 文件时,导入速度会相对慢一点。

3、用字符串格式的模块名导入模块

一条 import 或 from 语句中的模块名是直接编写的变量名称。然而,有时候,我们的程序可以在运行时以一个字符串的形式获取要导入的模块的名称。但是我们无法使用import 语句来直接载入以字符串形式给出其名称的一个模块,Python 期待一个变量名,而不是字符串。

为了解决这个问题,我们需要使用特殊的工具,从运行时生成的一个字符串来动态地载入一个模块。最通用的方法是,把一条导入语句构建为 Python 代码的一个字符串,并且将其传递给 exec() 内置函数以运行。

exec() 函数编译一个代码字符串,并且将其传给 Python 解释器以执行。exec() 唯一的缺点就是,每次运行时它必须编译 import 语句。如果需要运行很多次,使用内置的 __import__ 函数来从一个字符串名称载入,代码会运行的更快。


4、__name__ 和 __main__

每个模块都有个 __name__ 的内置属性,Python 会自动设置该属性:

如果文件是以顶层程序文件执行,在启动时,__name__ 就会设置为字符串 "__main__" 。

如果文件被导入,__name__ 就会被设成模块名。

结果就是模块可以检测自己的 __name__ 变量来确定它是在执行还是再导入。使用 __name__ 变量最常见的就是编写测试代码。简而言之,可以在文件末尾加个 __name__ 判断语句,把测试代码放到判断语句中。

编写既可以作为命名行工具也可以作为工具库使用的文件时,__name__ 技巧也很好用。


5、模块设计理念

就像函数一样,模块也有设计方面的折中考虑:需要思考哪些函数和类要放进模块以及模块通信机制等。

总是在Python的模块内编写代码:

没有办法写出不在某个模块之内的程序代码。事实上,在交互模式提示符下输入的程序代码,其实是存在于内置模块 __main__ 之内。交互模式提示符独特之处就在于程序是执行后就立刻丢弃,以及表达式结果是自动打印的。

加载模块会导致这个模块被"执行"。 也就是被导入模块的顶层代码将直接被执行。 这通常包括设定全局变量以及类和函数的声明。 如果有检查 __name__ 的操作,那么它也会被执行。
当然,这样的执行可能不是我们想要的结果。 你应该把尽可能多的代码封装到函数。 明确地说,只把函数和类定义放入模块的顶层是良好的模块编程习惯。

模块耦合要降到最低:

我们应该尽量的最小化模块间的耦合性。

模块应该少去修改其他模块的变量:

使用另一个模块定义的全局变量,这完全是可以的(毕竟这就是客户端导入服务的方式),但是,修改另一个模块内的全局变量,通常是出现设计问题的征兆。


6、顶层代码语句次序的重要性

当模块首次导入(或重载)时,Python会从头到尾的执行语句。在导入时,模块文件顶层的程序代码(不在函数内)就会被执行。因此,该语句是无法引用文件后面位置赋值的变量名。位于函数主体内的代码直到函数被调用后才会运行。因为函数内的变量名在函数实际执行前都不会解析,通常可以引用文件内任意地方的变量。


《Python基础手册》系列:

Python基础手册 1 —— Python语言介绍
Python基础手册 2 —— Python 环境搭建(Linux)
Python基础手册 3 —— Python解释器
Python基础手册 4 —— 文本结构
Python基础手册 5 —— 标识符和关键字
Python基础手册 6 —— 操作符
Python基础手册 7 —— 内建函数
Python基础手册 8 —— Python对象
Python基础手册 9 —— 数字类型
Python基础手册10 —— 序列(字符串)
Python基础手册11 —— 序列(元组&列表)
Python基础手册12 —— 序列(类型操作)
Python基础手册13 —— 映射(字典)
Python基础手册14 —— 集合
Python基础手册15 —— 解析
Python基础手册16 —— 文件
Python基础手册17 —— 简单语句
Python基础手册18 —— 复合语句(流程控制语句)
Python基础手册19 —— 迭代器
Python基础手册20 —— 生成器
Python基础手册21 —— 函数的定义
Python基础手册22 —— 函数的参数
Python基础手册23 —— 函数的调用
Python基础手册24 —— 函数中变量的作用域
Python基础手册25 —— 装饰器
Python基础手册26 —— 错误 & 异常
Python基础手册27 —— 模块
Python基础手册28 —— 模块的高级概念
Python基础手册29 —— 包

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 一、模块 1、模块和导入 当程序代码量变得相当大、逻辑结构变得非常复杂的时候,我们最好把代码按照逻辑和功能划分成一...
    常大鹏阅读 2,976评论 0 9
  • 定义类并创建实例 在Python中,类通过 class 关键字定义。以 Person 为例,定义一个Person类...
    绩重KF阅读 3,931评论 0 13
  • 1.双引号“”:完全匹配搜索 2.减号-:搜索不包含减号后面的词的页面。使用这个指令时减号前面必须是空格,减号后面...
    StormZang阅读 402评论 0 2
  • 安慰人的时候说的最多的一句话是 交给时间吧,总会过去的 可是只有过去了的人才会知道 永远都不会过去的 它是一根刺,...
    山名无山阅读 165评论 0 0
  • 由繁化简,归纳一个字。 由简出繁,诉说一篇章。 简简繁繁,繁繁简练, 这就是书,简繁书也!
    喃喃涅阅读 263评论 0 3