python模块和类在import上的区别

所谓的模块导入是指在一个模塊中使用另一个模块的代码的操作,它有利于代码的复用

也许你看到这个标题,会说我怎么会发这么基础的文章

与此相反。恰恰我觉嘚这篇文章的内容可以算是 Python 的进阶技能会深入地探讨并以真实案例讲解 Python import Hook 的知识点。

当然为了使文章更系统、全面前面会有小篇幅讲解基础知识点,但请你有耐心的往后读下去因为后面才是本篇文章的精华所在,希望你不要错过

导入单元有多种,可以是模块、包及变量等

对于这些基础的概念,对于新手还是有必要介绍一下它们的区别

Namespace packages 是由多个 部分 构成的,每个部分为父包增加一个子包各个部分鈳能处于文件系统的不同位置。部分也可能处于 zip 文件中、网络上或者 Python 在导入期间可以搜索的其他地方。命名空间包并不一定会直接对应箌文件系统中的对象;它们有可能是无实体表示的虚拟模块

命名空间包的 __path__属性不使用普通的列表。而是使用定制的可迭代类型如果其父包的路径 (或者最高层级包的 sys.path) 发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索

命名空间包没有 parent/__init__.py文件。實际上在导入搜索期间可能找到多个 parent 目录,每个都由不同的部分所提供因此 parent/one 的物理位置不一定与 parent/two 相邻。在这种情况下Python 将为顶级的 parent 包創建一个命名空间包,无论是它本身还是它的某个子包被导入

1.2 相对/绝对对导入

当我们 import 导入模块或包时,Python 提供两种导入方式:

B其中.表示當前模块,..表示上层模块你可以根据实际需要进行选择但有必要说明的是,在早期的版本( Python2.6 之前)Python 默认使用的相对导入。而后来的版夲中( Python2.6 之后)都以绝对导入为默认使用的导入方式。

使用绝对路径和相对路径各有利弊:

当你在开发维护自己的项目时应当使用相对蕗径导入,这样可以避免硬编码带来的麻烦而使用绝对路径,会让你模块导入结构更加清晰而且也避免了重名的包冲突而导入错误。

1.3 導入的标准写法

在 PEP8 中有一条对模块的导入顺序提出了要求,不同来源模块导入应该有清晰的界限,使用一空行来分开

import语句应当放在攵件头部,置于模块说明及docstring之后全局变量之前import语句应该按照顺序排列,每组之间用一个空格分隔按照内置模块,第三方模块自己所寫的模块调用顺序,同时每组内部按照字母表顺序排列# 内置模块

在 Python 中使用 import 关键字来实现模块/包的导入可以说是基础中的基础。

对于 __import__普通的开发者,可能就会比较陌生

和 import 不同的是,__import__是一个函数也正是因为这个原因,使得__import__的使用会更加灵活常常用于框架中,对于插件嘚动态加载

实际上,当我们调用 import 导入模块时其内部也是调用了 __import__,请看如下两种导入方法他们是等价的。

通过举一反三下面两种方法同样也是等价的。

上面我说 __import__常常用于插件的动态事实上也只有它能做到(相对于 import 来说)。

插件通常会位于某一特定的文件夹下在使鼡过程中,可能你并不会用到全部的插件也可能你会新增插件。

如果使用 import 关键字这种硬编码的方式显然太不优雅了,当你要新增/修改插件的时候都需要你修改代码。更合适的做法是将这些插件以配置的方式,写在配置文件中然后由代码去读取你的配置,动态导入伱要使用的插件即灵活又方便,也不容易出错

假如我的一个项目中,有 plugin01、plugin02、plugin03、plugin04四个插件这些插件下都会实现一个核心方法run。但有时候我不想使用全部的插件只想使用plugin02、plugin04,那我就在配置文件中写我要使用的两个插件

那我如何使用动态加载,并运行他们呢

在一个模塊内部重复引用另一个相同模块,实际并不会导入两次原因是在使用关键字 import导入模块时,它会先检索sys.modules里是否已经载入这个模块了如果巳经载入,则不会再次导入如果不存在,才会去检索导入这个模块

来实验一下,在 my_mod02这个模块里我 import 两次my_mod01这个模块,按逻辑每一次 import 会一佽my_mod01里的代码(即打印in mod01)但是验证结果是,只打印了一次

该现象的解释是:因为有 sys.modules的存在。

sys.modules是一个字典(key:模块名value:模块对象),它存放着在当前 namespace 所有已经导入的模块对象

运行结果如下,可见在 导入后 json 模块后sys.modules才有了 json 模块的对象。

由于有缓存的存在使得我们无法重噺载入一个模块。

但若你想反其道行之可以借助 importlib 这个神奇的库来实现。事实也确实有此场景比如在代码调试中,在发现代码有异常并修改后我们通常要重启服务再次载入程序。这时候若有了模块重载,就无比方便了修改完代码后也无需服务的重启,就能继续调试

还是以上面的例子来理解,my_mod02.py改写成如下

使用 python3 来执行这个模块与上面不同的是,这边执行了两次 my_mod01.py

如果指定名称的模块在 sys.modules找不到则将发起调用 Python 的导入协议以查找和加载该模块。

此协议由两个概念性模块构成即 查找器和加载器。

一个 Python 的模块的导入其实可以再细分为两个過程:

由查找器实现的模块查找由加载器实现的模块加载

4.1 查找器是什么?

查找器(finder)简单点说,查找器定义了一个模块查找机制让程序知道该如何找到对应的模块。

但这些查找器对应使用者来说并不是那么重要,因此在 Python 3.3 之前 Python 解释将其隐藏了,我们称之为隐式查找器

由于这点不利于开发者深入理解 import 机制,在 Python 3.3 后所有的模块导入机制都会通过 sys.meta_path 暴露,不会在有任何隐式导入机制

观察一下 Python 默认的这几种查找器 (finder),可以分为三种:

一种知道如何导入内置模块一种知道如何导入冻结模块一种知道如何导入来自 import path 的模块 (即 path based finder)那我们能不能自已萣义一个查找器呢?当然可以你只要:

定义一个实现了 find_module 方法的类(py2和py3均可),或者实现 find_loader 类方法(仅 py3 有效)如果找到模块需要返回一个 loader 對象或者 ModuleSpec 对象(后面会讲),没找到需要返回 None定义完后要使用这个查找器,必须注册它将其插入在 sys.meta_path 的首位,这样就能优先使用import sys

# 由于 finder 昰按顺序读取的,所以必须插入在首位

这里需要注意的是在 3.4 版前,查找器会直接返回 加载器(Loader)对象而在 3.4 版后,查找器则会返回模块規格说明(ModuleSpec)其中 包含加载器。

而关于什么是 加载器 和 模块规格说明 请继续往后看。

4.2 加载器是什么

查找器只负责查找定位找模,而嫃正负责加载模块的是加载器(loader)。

为什么这里说一般因为 loader 还分多种:

通过查看源码可知,不同的加载器的抽象方法各有不同

加载器通常由一个 查找器 返回。详情参见 PEP 302

那如何自定义我们自己的加载器呢?

定义一个实现了 load_module 方法的类对与导入有关的属性(点击查看详情)进行校验创建模块对象并绑定所有与导入相关的属性变量到该模块上将此模块保存到 sys.modules 中(顺序很重要避免递归导入)然后加载模块(這是核心)若加载出错,需要能够处理抛出异常( ImportError)若加载成功,则返回 module 对象若你想看具体的例子可以接着往后看。

4.3 模块的规格说明

導入机制在导入期间会使用有关每个模块的多种信息特别是加载之前。大多数信息都是所有模块通用的模块规格说明的目的是基于每個模块来封装这些导入相关信息。

模块的规格说明会作为模块对象的 __spec__属性对外公开有关模块规格的详细内容请参阅ModuleSpec。

在 Python 3.4 后查找器不再返回加载器,而是返回 ModuleSpec 对象它储存着更多的信息

模块名加载器模块绝对路径那如何查看一个模块的 ModuleSpec ?

从 ModuleSpec 中可以看到加载器是包含在内嘚,那我们如果要重新加载一个模块是不是又有了另一种思路了?

在 main.py处我加了一个断点,目的是当运行到断点处时我修改 my_info.py 里的 name 为ming,鉯便验证重载是否有效

从结果来看,重载是有效的

4.4 导入器是什么?

导入器(importer)也许你在其他文章里会见到它,但其实它并不是个新鮮的东西

它只是同时实现了查找器和加载器两种接口的对象,所以你可以说导入器(importer)是查找器(finder)也可以说它是加载器(loader)。

由于 Python 默认的 查找器和加载器 仅支持本地的模块的导入并不支持实现远程模块的导入。

为了让你更好的理解 Python Import Hook 机制我下面会通过实例演示,如哬自己实现远程导入模块的导入器

5.1 动手实现导入器

当导入一个包的时候,Python 解释器首先会从 sys.meta_path 中拿到查找器列表

默认顺序是:内建模块查找器 -> 冻结模块查找器 -> 第三方模块路径(本地的 sys.path)查找器

若经过这三个查找器,仍然无法查找到所需的模块则会抛出ImportError异常。

因此要实现远程导入模块有两种思路。

一种是实现自己的元路径导入器;另一种是编写一个钩子添加到sys.path_hooks里,识别特定的目录命名模式我这里选择苐一种方法来做为示例。

实现导入器我们需要分别查找器和加载器。

由源码得知路径查找器分为两种

# 不是原定义的url就直接返回不存在

若使用 find_spec,要注意此方法的调用需要带有两到三个参数

第一个是被导入模块的完整限定名称,例如 foo.bar.baz第二个参数是供模块搜索使用的路径條目。对于最高层级模块第二个参数为None,但对于子模块或子包第二个参数为父包__path__属性的值。如果相应的__path__属性无法访问将引发ModuleNotFoundError。第三個参数是一个将被作为稍后加载目标的现有模块对象导入系统仅会在重加载期间传入一个目标模块。

# 不是原定义的url就直接返回不存在

由源码得知路径查找器分为两种

在 SourceLoader 这个抽象类里,有几个很重要的方法在你写实现加载器的时候需要注意

get_code:获取源代码,可以根据自己場景实现实现exec_module:执行源代码,并将变量赋值给 module.dictget_data:抽象方法必须实现,返回指定路径的字节码get_filename:抽象方法,必须实现返回文件名在┅些老的博客文章中,你会经常看到 加载器 要实现 load_module而这个方法早已在 Python 3.4 的时候就被废弃了,当然为了兼容考虑你若使用load_module也是可以的。

当伱使用这种旧模式实现自己的加载时你需要注意两点,很重要:

execute_module 必须重载而且不应该有任何逻辑,即使它并不是抽象方法load_module,需要你茬查找器里手动执行才能实现模块的加载。做为替换,你应该使用

查找器和加载器都有了别忘了往sys.meta_path 注册我们自定义的查找器(UrlMetaFinder)。

所有的代码都解析完毕后我们将其整理在一个模块(my_importer.py)中

# 不是原定义的url就直接返回不存在

5.2 搭建远程服务端

最开始我说了,要实现一个远程导入模块的方法

我还缺一个在远端的服务器,来存放我的模块为了方便,我使用python自带的 http.server模块用一条命令即可实现

一切准备好,我們就可以验证了

至此,我实现了一个简易的可以导入远程服务器上的模块的导入器

}

python的流行主要依赖于其有众多功能強大的库(Library)Python自带的标准库(Standard Library)可以满足大多数的基本需求,除了函数库以外模块(Module)和包(Package)也常会被提及。其中库、模块和包常瑺会分不清谁是谁今天就一起来学习下

模块是一种以.py为后缀的文件,在.py文件中定义了一些常量和函数模块的名称是该.py文件的名称。模塊的名称作为一个全局变量name的取值可以被其他模块获取或导入模块的导入通过ipmort来实现,导入模块的方式如下:

包体现了模块的结构化管悝思想包由模块文件构成,将众多具有相关功能的模块文件结构化组合形成包从编程开发的角度看,两个开发者A和B由可能把各自开发苴功能不同的模块文件取了相同的名字如果第三个开发者通过名称导入模块,则无法确认是哪个模块被导入了为此,开发这A和B可以構建一个包将模块放到包文件夹下,通过“包.模块名”来指定模块示例:

一个包文件一半由init.py和其他诸多.py文件构成。该init.py内容可以为空囿额可以写入一些包执行时的初始化代码。init.py是包的标志性文件Python通过一个文件夹下是否有init.py文件,来识别促文件夹是否为包文件

Python中的库是借用其他编程语言的概念,没有特别具体的定义Python库着重强调其功能性。在Python中具有某些功能的模块和包都可以被称作库。模块有诸多函數组成包由诸多模块机构化组成,库中也可以包含包、模块和函数

如果你想更多的了解Python,点击以下视频跳转链接:

}

 模块用来从逻辑上组织python代码(变量函数,类逻辑:实现一个功能),本质就是.py结尾的python文件(文件名:test.py对应的模块名:test)

包:用来从逻辑上组织模块的代码,本质僦是一个目录(必须带有一个__init__.py文件)

   ==>导入多个脚本模块

   ==>这里相当于导入module_alex脚本文件里的所有代码

 ==>把模块的所有代码解释了┅遍,赋值给module_alex

 B、import本质(路径搜索和搜索路径)

  # 导入模块的过程:找文件找文件所在的路径,找sys.path

  ==>所以 我们需要把当前脚本的绝對路径的父父路径加到sys.path里去,这样就可以找到其他模块

 D、然后再通过os.path.dirname可以获取到除文件名以外的路径,我们使用2次就可以得到父父级目录,现在我们获得了父父级目录我们把目录加到 sys.path去,但是我们注意我们需要把这个目录放到列表sys.path的第一个位置去~!

 E、导入包的夲质就是执行该包下面的__init__.py文件

}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信