一、前言
本文基于开源项目:
//pyth…
需要进一步进行补充并扩展相关的解说环节, 目的在于期望能够使得广大阅读者通过阅读这一篇文章就足以完全搞清楚其中的运作机制。
1.1. 我们首先要来了解一下, 到底什么是机制。
通常来说, 如果在一段代码里面去执行那个引用自另一个模块中的代码的话, 那么就需要使用到一种叫做某种机制的东西了。具体地说, 有个什么语句, 是使用这种所谓的机制的最为常见的一种手段, 不过要提醒大家注意一下, 这个所谓的常用手段, 它并不是说唯一的。
函数也能够被用来将其它模块的代码引入进来。
1.那第2个步骤, 具体是怎么进行操作的?
这个语句它会去执行两个步骤的动作, 具体来说是这样子的。
搜索的步骤实际上是通过那个函数来完成的, 然后函数的返回值会被当作一个变量绑定到局部变量里面去。在下面这个段落部分我们会详细地去讲述那个函数它是怎么去进行工作的细节的。
二、 机制概览
下图是 机制的一个概览的图。从这张图中不难看出来, 当这个 机制被触发的时候, 首先需要去 sys. 里面查找一下, 看这个模块是不是已经被引进来过。如果觉得这个模块确实已经被引进了, 就会直接调用它。如果没有被引进过, 才会继续往后面走这一步。
这里的 sys. 能够被看成是一个存放缓存内容的容器。另外有一点需要特别注意的是, 如果在这个 sys. 里面查找到对应的结果是 None 的话, 那么就会导致一个 异常被抛出来。这是一个简单的实验例子。
In : sysIn : sys. = : os--------------------------------------------------------------------------- (most call last) in ----> 1 r: of os ; None in sys.
如果在 sys. 中找到了对应的数据, 并且这个数据是由某些语句所触发的, 那么接下来的一步操作, 就会把与该数据相关的变量进行绑定, 并将它们赋值给局部变量。
如果系统没有探测到任何已经被存储起来的数据, 它就需要执行一个从头开始的完整处理流程。在进行这个流程的时候, 系统会去查看sys.这个地方, 目的是检查有没有满足要求的那种按照特殊路线来找东西的工具。
sys.在这里是专门用来保存这些工具的列表名字。在这个列表里面, 一开始就有三个标准的工具可以使用:
In : sysIn : sys.:
., ., .
查找器的具体方法, 直接决定了这个查找器能不能处理那些要引入的模块对象, 并且还能不能返回一个包含用来加载当前这个模块所需要的各种相关信息的对象。要是没法返回一个合适的对象, 系统就得去看一眼 sys 列表里面的下一个元路径查找器是谁了。
要是遍历了一遍 sys, 最后都没找到哪个元路径匹配得上, 那系统就直接抛出异常。一旦你引入一个根本就不存在的模块, 这种情况就发生了, 原因在于在 sys 里面所有的查找器, 都没有能力去处理这种局面。
In : --------------------------------------------------------------------------- (most call last) in ----> 1 : No named ''
但是, 要是手动给它添加一个专门用于处理该模块的查找方法之后, 那么这个方法就是能够被引进来的。
在导入系统模块的这一部分代码流程里面, 我们可以看到具体的文件路径包含 abc 模块, 同时还引入了其他相关的包内容, 接着定义了一个空的类结构, 并且在类内部声明了一个方法的函数体, 这个方法的参数列表里面有 self、path 以及一个默认值为 None 的可选参数, 方法体的执行过程是调用了某个空字符串加上 None 值的一个操作组合, 最后我们要注意这段代码并不是建议大家在日常开发中直接这样去做的, 而是需要对系统对象的某个属性进行最终的赋值设定处理。
()
...: ...: --------------------------------------------------------------------------- (most call last) in 11 sys. =
()
从12变为13时出现了错误。
我们能够通过观察得出一个明确的结论, 那就是当系统接收到关于具体执行何种操作的指令时, 是不会去抛出特定的那种异常的, 但是需要知道的是, 想要成功实现一个模块的加载过程, 那么还必须得要依赖于相应的加载器。
加载器这个东西, 它是对象里面的一个属性, 它的作用在于决定了怎么样去加载以及怎么样去执行一个模块。如果说把对象比喻成是“师父领进门”, 那么加载器就可以被比喻成是“修行在个人”。
在加载器这个范畴里, 你完全有能力来决定怎样来加载模块, 又怎样来执行模块。这里面的决定权, 它不仅是指加载和执行模块本身, 你甚至可以动手去修改一个模块。
在提示符为 In 的代码输入环境中, 代码执行了多行导入操作, 分别从 sys 这个模块中进行了引用, 接着又使用了三个省略号来表示的相对导入路径, 其中第二个省略号代表当前位置, 第三个省略号和第一个逗号一起出现了, 然后定义了一个不包含名字的类, 在这个类的内部定义了一个以 self 和 name 作为参数的方法, 该方法主体包括设置 self.x 的值为 1 这样的语句以及把参数 name 赋值给 self.name 的操作, 紧接着又定义了另一个同样未命名的类, 这个类里包含一个接收 self 和 spec 作为参数的方法, 在方法体内调用了以 spec.name 作为参数的代码块, 接下来再定义了一个函数, 该函数没有列出它的名字, 但有一个自引用的空参数列表, 方法体里包含了将某个变量 y 设置为 2 的操作, 随后又出现了一种类的定义, 还是没取名, 但它的方法接收了 self、两个占位符号代表的参数、path 以及带有默认值 None 的最后一种类型的指定项, 在执行时调用了一个名为 的内容, 传入的是一个空字符串跟一个括号括起来的元素列表组成的二元组。
()
在输入的内容中, 存在一个括号, 并且在大于符号后面的数值输出了一点一, 接着在另一个输入的点上, y坐标输出的是二。
通过观察上面呈现出的这些具体例子, 我们不难发现, 在定义一个加载器的时候, 通常都有两个特别重要的方法是需要开发者去实现的。并且, 一旦你选择实现了那第一个提到的方法, 那么第二个提到的方法也就变成了不可或缺的部分, 它是必须得被实现出来的。
如果整个这个导入模块的机制, 它是由程序中的 语句直接发起并触发的, 那么在代码运行结束后, 由那个负责加载的方法返回来的模块对象所代表的变量名称, 将会被自动地绑定并关联到当前所处的局部作用域中的变量上。
与此同时, 如果某个模块借助于这种方式成功地完成了加载的过程, 那么这个已经加载成功的模块就会被保存在缓存系统里面, 而保存它的地方就是sys模块里提供的那个用来存放已加载模块信息的字典结构之中。假如说这个模块再一次被加载的话, 那么系统里的缓存部分将会被直接引用到。
三、 勾子( hooks)
为了让整体内容更加简洁明了, 在先前给出的流程示意图之中, 是没有将相关的机制的钩子纳入进去的。但是从实际操作层面来看, 大家是完全有能力去增加一个勾子, 进而用于修改系统模块的全局属性以及系统路径变量的, 这么做最终达成改变系统加载规则行为的目的。
在前边展示的那个案例里面呢, 大家是直接对系统的模块对象完成了修改处理的。不过需要明确的是, 大家还能够借助于设置钩子的方式来实现相同效果:
In : sys ...: from types ...: from . ...: from .abc , ...: ...: class (): ...: def (self, name): ...: self.x = 1 ...: self.name = name ...: ...: class (): ...: def (self, spec): ...: (spec.name) ...: ...: def (self, ): ...: .y = 2 ...: ...: class (): ...: def (self, , path, =None): ...: ('', ()) ...: ...: def (path): ...: # some here ...: () ...: ...: sys. =
...: # force to use the hook ...: sys..clear() ...: ...: ...: : )>四、元路径查找器(meta path )
元路径查找器的一个主要职责, 就是查看是否能找到模块。这些查找器被存放在 之中, 以便进行遍历操作。当然, 它们也可以通过钩子来返回相关结果, 这一点可以参考上面所展示的例子内容。每一个负责查找的组件都必须去实现对应的处理方法。
如果某个查找器清楚应该如何处理即将要引入进来的模块结构, 那么它就会返回一个相应的对象实例供后续使用, 具体的介绍可以参见下一节的说明内容。倘若它并不知道该如何处理这种情况, 那就只能选择返回空值 None 以便让系统知晓当前阶段无法提供有效支持。
就像前面已经说过的那样,sys里面包含了三种查找器:
咱们这里头想要把那个重点给落在怎么个事儿上, 那就聊聊这个基于路径的查找器, 也就是说是path based 这个东西, 它呢是用于去搜索那一串子的一个个的那个路径的, 再说了每个路径都是拿来查找那边儿对应的模块能不能够加载的那个玩意儿的。
默认的路径查找器实现了所有在文件系统的特殊文件中查找模块的功能, 这些特殊文件包括 源文件(.py 文件), 编译后代码文件(.pyc 文件), 共享库文件(.so 文件)。如果标准库中包含, 那么相关的文件也可用来查找可引入的模块。
路径查找器这个东西, 它可不仅仅是只在咱们平时用的那个文件系统里头去找那些文件, 它还能去查那个 URL 数据库里面的东西, 再不然, 任何那种可以拿来用一串字符串表示出来的地址, 它都能搞定。
你可以用上节当中所提供的那些勾子, 来把模块查找给实现到同类型地址上面。比如, 你想要通过 URL 的方式, 去对某一个东西进行解析和查找, 那么你就可以去编写一个特定的勾子, 这个勾子的作用是用来对那个 URL 进行解析工作。
等解析完成之后, 它会返回回来一个能够执行路径查找工作的路径查找器, 这样一来, 整个操作流程就走通了。
请注意, 路径查找器与元路径查找器是不一样的东西。元路径查找器是在 sys 里面被用来遍历的。而路径查找器则是专门指那些基于路径去进行查找的工具类型。
五、 对象
每个元路径查找器必须实现在那个位置所要求的处理模块的方法, 如果该查找器知道了要如何处理这个需要引入进来的模块的话, 这个方法将返回一个由查找器产生的对象, 这里说的对象有两个特别值得去注意的属性, 其中一个是模块的名字, 另一个就是产生这个对象的查找器自身,如果说由这样一个对象所带有的查找器的值是空值或者是未定义的状态, 那么系统将抛出一个和名称相关的异常出来, 接下来的内容里会详细介绍如何用这个查找器去创建以及执行具体的模块工作。
你可以通过输入一个点号来对模块的相关对象进行查找操作的。
在输入中, 系统输入的部分显示名称是sys这一项, 接着是系统的冒号符号包含空白的等式关系, 随后出现第六个标题为加载器, 并且后面跟着圆括号。
加载器使用一种方式来创建模块, 同时又使用另一种方式来执行这个模块。通常说来, 如果一个模块是一个非内置的模块, 也或者是那种动态扩展出来的模块, 那么该模块里面的代码就需要在一个特定的空间上运行, 这个地方就是它的代码空间。
如果这些代码没办法顺利运行下去, 那么系统就会抛出一个异常出来, 另外一些在执行过程中出现的异常情况, 也会被当作异常处理而抛出来。
在绝大多数的常规情形之下, 查找器以及加载器实际上指的是完全一致的同一个东西。针对此种具体情况而言, 查找器所拥有的相关方法会进行返回操作, 所返回的那个特定对象内部的某些属性值, 将会直接指向它自身。
我们可以用该功能来动态创建出一个模块, 当它的返回结果是 none 的之后, 就会自动地去创建一个模块出来。
七、总结
这个机制灵活而强大。上面的介绍主要是根据官方文档, 还有更新的3.6+版本号写的。因为篇幅限制, 所以很多细节没有写进去, 比如子模块怎么加载的, 模块代码缓存是怎么做的。文章里也可能出错, 如果有任何疑问, 欢迎去//pyth…开issue提问和讨论。
至此, 关于入门基础部分中涉及到的机制的相关内容, 已经向大家介绍完毕。如果还想了解其他与机制有关的内容, 建议可以通过搜索之前的文章进行查看, 也可以继续向下浏览本站为您推荐的相关文章。在未来的日子里, 还希望大家给予我们持续的支持与关注。