IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> python导入系统 -> 正文阅读

[Python知识库]python导入系统

参考 python3.6.15的导入机制文档

前言

python的导入系统一直在变化,所以具体版本的导入系统,应该去查看文档。本文并不是文档,本文的目的是为了探究python的模块导入过程。

概念

module object:这是python关于模块的唯一的一个数据模型对象,所有的模块都是该对象,不管该模块是一个模块还是一个包。

模块:可以简单的理解为文件系统中的一个.py文件。

包:可以简单的理解为文件系统中的目录,该目录带有一个__init__.py文件。

regular packages和namespace?packages

regular packages:常规包,该种类型的包只存在于当前文件系统中,上面所说的包就是这种。平常咱使用的也就是这种包了。

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

导入系统的导入方法

import语句:from xxxx import xxxx 和import xxxx

__import__():这是一个built-in函数。

importlib:该库提供了一系列的用于导入系统的API,importlib.import_module()就是一个专门用于导入模块的API。需要注意的是,该库提供的API,实际上都是导入系统的功能,这里只是给用户提供的使用接口。

几种导入方式对比

import语句包含两个过程:第一阶段在内部调用__import__()来搜寻模块并创建模块对象;第二阶段就是绑定名字到该模块对象。

__import__()只是用于搜寻模块并返回模块对象,但是其根据参数的不同或返回顶层模块对象,或返回指定的模块对象。

importlib.imoprt_module()用于搜索指定的模块,并返回指定的模块对象。

# __import__(name,?globals=None,?locals=None,?fromlist=(),?level=0)函数

该函数会导入?name?模块,有可能使用给定的?globals?和?locals?来确定如何在包的上下文中解读名称。?fromlist?给出了应该从由?name?指定的模块导入对象或子模块的名称。 标准实现完全不使用其?locals?参数,而仅使用?globals?参数来确定?import?语句的包上下文。

level?指定是使用绝对还是相对导入。?0?(默认值) 意味着仅执行绝对导入。?level?为正数值表示相对于模块调用?__import__()?的目录,将要搜索的父目录层数 (详情参见?PEP 328)。

当?name?变量的形式为?package.module?时,通常将会返回最高层级的包(第一个点号之前的名称),而?不是?以?name?命名的模块。 但是,当给出了非空的?fromlist?参数时,则将返回以?name?命名的模块。

__import__()调用的影响:

1、如果一个模块没有被载入过,那么__import__会创建该模块对象,并在sys.module中以完整的模块名,如模块名为A.B.C为键值来缓存该模块。

2、如果模块名是A.B.C这种形式,则__import__还会同时载入A模块和A.B模块,并将它们缓存到sys.modules中。这是__import__带来的额外影响,它加载C模块的父模块B,又加载了B模块的父模块A,也就是说它会导入父模块,直到最顶层模块为止。

# import语句

形式一、import xxxx

import spam

这一条语句就相当于如下的__import__调用:

spam = __import__('spam', globals(), locals(), [], 0)

返回了spam模块对象之后,python将spam这个模块对象绑定到本地的spam名字。

import spam.ham

这一条语句就相当于如下的__import__调用:

spam = __import__('spam.ham', globals(), locals(), [], 0)

仍然返回了spam模块对象,python随后将在本地名字空间中绑定spam名字到spam模块对象。

由于fromlist的参数为0,所以上面的__import__调用都返回了最顶层的模块对象。

形式二、from xxx import xxxx

from?spam.ham?import?eggs,?sausage?as?saus

这一条语句就相当于如下的__import__调用:

_temp = __import__('spam.ham', globals(), locals(), ['eggs', 'sausage'], 0)
eggs = _temp.eggs
saus = _temp.sausage

在这里,__import__会返回spam.ham模块对象,然后在本地名字空间中绑定了eggs名字到spam.ham.eggs模块对象,同时绑定了saus名字到spam.ham.saus模块对象。

由于fromlist的参数不为0,所以上面的__import__调用返回了特定的模块对象。

# importlib.import_module()

导入一个模块。参数?name?指定了以绝对或相对导入方式导入什么模块 (比如要么像这样?pkg.mod?或者这样?..mod)。如果参数 name 使用相对导入的方式来指定,那么那个参数?packages?必须设置为那个包名,这个包名作为解析这个包名的锚点 (比如?import_module('..mod',?'pkg.subpkg')?将会导入?pkg.mod)。

import_module()?函数是一个对?importlib.__import__()?进行简化的包装器。 这意味着该函数的所有特性都来自于?importlib.__import__()。 这两个函数之间最重要的不同点在于?import_module()?返回指定的包或模块 (例如?pkg.mod),而?__import__()?返回最高层级的包或模块 (例如?pkg,fromlist参数为空的时候,会返回最顶层的包或模块)。

正因为这是对__import__的封装,所以其也会导入父模块,并缓存到sys.modules中。

如果想要动态加载某个模块,importlib.import_module()无疑是最好的选择。

可以看到,这三种方式,实际上最终都是调用__import__函数完成模块的搜寻与模块对象的创建,然后加载执行模块代码。

导入系统的细节

__import__会根据参数name来搜寻模块,那么它究竟是怎样搜寻的呢?

其实这取决于三个条件:

1、参数name的值。

2、缓存sys.modules中是否存在该模块。

3、finder与loader。

# 参数name的值

如果name是类似于foo这样的值,那么python只会搜寻foo,并发导入foo。

如果name是类似于foo.bar.baz这样的值,那么python会先搜寻foo,导入foo;再搜寻foo.bar,导入foo.bar;最后搜寻foo.bar.baz,导入foo.bar.baz。

# 模块缓存sys.modules

搜寻模块的第一个地方就是模块缓存sys.modules。

这个映射起到缓存之前导入的所有模块的作用(包括其中间路径)。 因此如果之前导入过?foo.bar.baz,则?sys.modules?将包含?foo,?foo.bar?和?foo.bar.baz?条目。 每个键的值就是相应的模块对象。

在导入期间,会在?sys.modules?查找模块名称,如存在则其关联的值就是需要导入的模块,导入过程完成。 然而,如果值为?None,则会引发?ModuleNotFoundError。 如果找不到指定模块名称,Python 将调用finder继续搜索该模块。

# finder与loader

当在缓存中找不到对应的模块的时候,就会调用finder去寻找对应的模块对象。

python总共有3个默认的finder:

内置模块finder

frozen? module finder

搜索import path的finder

frozen module:

使用python编写,然后编译为字节码,最后直接把字节码编译进python虚拟机的一种模块。

frozen module finder就是用来搜寻这种模块的。

内置模块:

对于CPython来说,内置模块就是使用C编写,直接编译进python虚拟机的那些模块,通过打印sys.builtin_module_names可以获知当前python所支持的所有内置模块。一般常见的有:sys,_json,_datatime。

内置模块finder就是专门用来搜寻内置模块的。

对于标准库的模块,第三方模块,用户自定义模块:

这些模块其实都有对应的文件存在于文件系统中,所以它们都是有路径的。

import path finder就是专门用来搜寻这种模块的。

这三种finder的调用顺序,那自然而然是优先调用内置模块finder,然后是frozen module finder,最后是import path finder。

在 3.4 版更改:?在之前的 Python 版本中,查找器会直接返回?loader,现在它们则返回模块规格说明,其中?包含?loader。 loader仍然在导入期间被使用,但负担的任务有所减少。

python的导入机制是可扩展的,它的扩展性通过import hooks实现。

# import hooks

导入机制被设计为可扩展;其中的基本机制是 import hooks。 import hooks有两种类型: meta hooks?和 import path hooks

meta hooks通过在sys.meta_path中添加finder实现,在sys.modules查找完毕之后,meta hooks就能获得执行。meta hooks的运行实际在sys.modules查找之后,在内置模块finder,forzen module finder等之前运行。

import path hooks是作为sys.path(或者package.__path__)过程的一部分,在遇到hooks所hook的路径之后,import hooks获得运行。import hooks通过在sys.path_hooks中添加可调用对象来实现注册。

现在,finder和imoprt hooks都有了,finder可以为用户寻找模块,import hooks给用户提供了自定义载入过程的能力,它们相互配合完成对模块的搜寻,然后通过loader完成对模块的加载与执行。

那么它们是如何 一步步得到调用的呢?

# meta path

当指定名称的模块在?sys.modules?中找不到时,Python 会接着搜索?sys.meta_path,其中包含meta_path finder对象列表。 这些finder按顺序被查询,以确定它们是否知道如何处理该名称的模块。meta_path finder必须实现名为?find_spec()?的方法,该方法接受三个参数:名称、导入路径和目标模块(可选)。 meta_path finder可使用任何策略来确定它是否能处理指定名称的模块。

如果meta_path finder知道如何处理指定名称的模块,它将返回一个module spec。 如果它不能处理该名称的模块,则会返回?None。 如果?sys.meta_path?处理过程到达列表末尾仍未返回说明对象,则将引发?ModuleNotFoundError。 任何其他被引发异常将直接向上传播,并放弃导入过程。

meta_path finder包含3个默认的finder,就是前面所说的那3个。

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

对于单个导入请求可以多次遍历元路径。 例如,假设所涉及的模块都尚未被缓存,则导入?foo.bar.baz?将首先执行顶级的导入,在每个元路径查找器 (mpf) 上调用?mpf.find_spec("foo",?None,?None)。 在导入?foo?之后,foo.bar?将通过第二次遍历元路径来导入,调用?mpf.find_spec("foo.bar",?foo.__path__,?None)。 一旦?foo.bar?完成导入,最后一次遍历将调用?mpf.find_spec("foo.bar.baz",?foo.bar.__path__,?None)

在 3.4 版更改:?元路径查找器的?find_spec()?方法替代了?find_module(),后者现已弃用,它将继续可用但不会再做改变,导入机制仅会在查找器未实现?find_spec()?时尝试使用它。

对于前面两种finder来说,由于python虚拟机一旦编译完成之后,基本不会有太多更改,因此,其行为也就基本固定了;对于第三种finder来说,其行为往往与path相关,那么它的path搜索范围从哪里来呢?

# import path finder

imoprt path finder是一个path based finder,它会搜索一个imoprt path,该import path包含一个path entries列表,每一个path entry都表示了一个模块的搜索路径。

该finder会遍历路径列表的路径,为每一个path entry调用path entry finder。

path entry finder最终完成对路径的查找。

正如上面所说,path entry finder最终完成对路径的查找,路径来自path entries 列表,那么import path 中的path entries列表又从哪里来呢?

import path中的path entries列表在python导入系统初始化的时候构建,一般都从sys.path中获得。如果被载入的模块是一个子包,那么该path entries就会从父包的__path__构建。

到这里,python的模块搜索路径一下就弄清了,原来刚开始学习python的时候,一直只知道如果需要载入添加自定义的模块,就必须在sys.path中添加对应的路径,这里直接已经说明了更深层次的原因了。同时,这里也弄清了子包的载入过程了。

为什么载入子包,父包也会被同时载入?

这是因为载入子包需要访问父包的__path__属性,从该__path__构建搜索路径。而要方位父包的__path__属性,就必须现在载入父包。

好了,现在知道python是如何一步一步运行到finder的,如果finder找到了目标模块,那么就会返回一个module spec,可是它与loader似乎没有什么关系啊?loader究竟是如何得到调用的?

先来看看module spec包含什么:

# module spec

New in version 3.4.

name

(__name__)

A string for the fully-qualified name of the module.

loader

(__loader__)

The loader to use for loading. For namespace packages this should be set to?None.

origin

(__file__)

Name of the place from which the module is loaded, e.g. “builtin” for built-in modules and the filename for modules loaded from source. Normally “origin” should be set, but it may be?None?(the default) which indicates it is unspecified.

submodule_search_locations

(__path__)

List of strings for where to find submodules, if a package (None?otherwise).

loader_state

Container of extra module-specific data for use during loading (or?None).

cached

(__cached__)

String for where the compiled module should be stored (or?None).

parent

(__package__)

(Read-only) Fully-qualified name of the package to which the module belongs as a submodule (or?None).

has_location

Boolean indicating whether or not the module’s “origin” attribute refers to a loadable location.

其中,重点关注__name__,__loader__。

__name__:模块名。

__loader__:用来加载该模块的loader。

原来finder将对应的模块loader放在module spec中一起返回了。

当python知道了一个模块的module spec的时候,就会紧接着调用对应的loader。

#loader

loader提供了关键的模块加载功能,它会创建模块对象,然后将该module放入到缓存sys.modules中,接着执行该模块代码。

小结:

一次__import__可能会包含多次的模块查找(载入的模块是子模块的话,需要先载入父包),这就意味着会有多次的finder和loader的执行,当这些finder和对应的loader都执行完毕之后,__import__最后根据情况返回一个模块对象。

其他细节

#子模块

当使用任意机制 (例如?importlib?API,?import?及?import-from?语句或者内置的?__import__()) 加载一个子模块时,父模块的命名空间中会添加一个对子模块对象的绑定。 例如,如果包?spam?有一个子模块?foo,则在导入?spam.foo?之后,spam?将具有一个 绑定到相应子模块的?foo?属性。

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-04-28 11:48:54  更:2022-04-28 11:50:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 16:01:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码