iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >python的import 机制是怎么实现的
  • 592
分享到

python的import 机制是怎么实现的

2024-04-02 19:04:59 592人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

目录import 机制功能单模块导入级联导入from & importimport & asfrom & import & as与module对象有关

import 机制功能

Python 的 import 机制基本上可以切分为三个不同的功能:

  • python运行时的全局模块池的维护和搜索;
  • 解析与搜索模块路径的树状结构;
  • 对不同文件格式的模块执行动态加载机制;

尽管 import 的表现形式千变万化,但是都可以归结为:import x.y.z 的形式,当然 import sys 也可以看成是 x.y.z 的一种特殊形式。而诸如 from、as 与 import 的结合,实际上同样会进行 import x.y.z 的动作,只是最后在当前名字空间中引入的符号各有不同。

然后导入模块,虚拟机会调用 __import__,那么我们就来看看这个函数长什么样子。

static PyObject *
builtin___import__(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"name", "globals", "locals", "fromlist",
                             "level", 0};
    //初始化globals、fromlist都为NULL
    PyObject *name, *globals = NULL, *locals = NULL, *fromlist = NULL;
    int level = 0;//表示默认绝对导入
  
    //从PyTupleObject中解析出需要的信息
    if (!PyArg_ParseTupleAndKeyWords(args, kwds, "U|OOOi:__import__",
                    kwlist, &name, &globals, &locals, &fromlist, &level))
        return NULL;
    //导入模块
    return PyImport_ImportModuleLevelObject(name, globals, locals,
                                            fromlist, level);
}

里面有一个PyArg_ParseTupleAndKeywords函数,我们需要提一下,它在虚拟机中是一个被广泛使用的函数,原型如下:

//Python/getargs.c
int PyArg_ParseTupleAndKeywords(PyObject *, PyObject *,
                                const char *, char **, ...);

这个函数的作用是参数解析,负责将 args 和 kwds 中所包含的所有对象(指针)按指定的格式 fORMat 解析成各种目标对象,可以是 Python 的对象,例如 PyListObject、PyLonGobject,也可以是 C 的原生对象。

我们知道这个 builtin__import__ 里面的参数 args 指向一个 PyTupleObject ,包含了 import 函数运行所需要的参数和信息,它是虚拟机在执行 IMPORT_NAME 指令的时候打包产生的。

然而在这里,虚拟机进行了一个逆动作,将打包后的这个 PyTupleObject 拆开,重新获得当初的参数。Python 在自身的实现中大量使用了这样的打包、拆包策略,使得可变数量的对象能够很容易地在函数之间传递。

该系列完结后,会介绍如何用 C 给 Python 写扩展,到时候会剖析这个函数的用法。

在完成了对参数的拆包动作之后,会进入 PyImport_ImportModuleLevelObject ,这个我们在 import_name 中已经看到了,当然它内部也是调用了 __import__。

另外每个包和模块都有一个__name__和__path__属性。

import numpy as np
import numpy.core
import six
print(np.__name__, np.__path__) 
"""
numpy ['C:\\python38\\lib\\site-packages\\numpy']
"""
print(np.core.__name__, np.core.__path__)
"""
numpy.core ['C:\\python38\\lib\\site-packages\\numpy\\core']
"""
print(six.__name__, six.__path__) 
"""
six []
"""

name__就是模块名或者包名,如果是包下面的包或者模块,那么就是包名.包名或者包名.模块名;至于__path__则是包所在的路径,对于模块而言, __path 为空列表。

此外还有一个 file 属性,对于模块而言就是其自身的完整路径;对于包而言则分两种情况,如果包内部存在 __init__.py 文件,那么得到的就是 __init__.py 文件的完整路径,没有则为 None。

下面来看一下不同的导入方式对应的字节码,然后在虚拟机的层面来理解这些导入方式。

单模块导入

以一个简单的模块导入为例:

import sys
"""
  0 LOAD_CONST               0 (0)
  2 LOAD_CONST               1 (None)
  4 IMPORT_NAME              0 (sys)
  6 STORE_NAME               0 (sys)
  8 LOAD_CONST               1 (None)
 10 RETURN_VALUE
"""

这是我们一开始考察的例子,现在我们已经很清楚地了解了 IMPORT_NAME 的行为。在 IMPORT_NAME 指令的最后,虚拟机会将 PyModuleObject对象(指针)压入到运行时栈,随后会将 <"sys", PyModuleObject *> 存放到当前的 local名字空间中。

级联导入

import sklearn.linear_model.ridge
"""
  0 LOAD_CONST               0 (0)
  2 LOAD_CONST               1 (None)
  4 IMPORT_NAME              0 (sklearn.linear_model.ridge)
  6 STORE_NAME               1 (sklearn)
  8 LOAD_CONST               1 (None)
 10 RETURN_VALUE
"""

如果是级联导入,那么 IMPORT_NAME 的指令参数则是完整的路径信息,该指令的内部将解析这个路径,并为 sklearn, sklearn.linear_model, sklearn.linear_model.ridge都创建一个 PyModuleObject 对象,这三者都存在于 sys.modules 里面。

但是我们看到 STORE_NAME 是 sklearn,表示只有 sklearn 这个符号暴露在了当前模块的 local 空间里面。可为什么是sklearn呢?难道不应该是 sklearn.linear_model.ridge 吗?

其实经过我们之前的分析这一点已经不再是问题了,因为 import sklearn.linear_model.ridge并不是说导入一个模块或包叫做 sklearn.linear_model.ridge,而是先导入 sklearn,然后把 linear_model 放在 sklearn 的属性字典里面,再把 ridge 放在 linear_model 的属性字典里面。

同理 sklearn.linear_model.ridge 代表的是先从 local 空间里面找到 sklearn,再从 sklearn 的属性字典中找到 linear_model,然后在 linear_model 的属性字典里面找到ridge。因为 linear_model 和 ridge 已经在相应的属性字典里面,我们通过 sklearn 一级一级往下找是可以找到的,因此只需要将符号 skearn 暴露给 local 空间即可。

或者说暴露 sklearn.linear_model.ridge 本身就是不合理的,因为这表示导入一个名字就叫做 sklearn.linear_model.ridge 的模块或者包,但显然不存在。而即便我们创建了这样的一个模块或包,由于 Python 的语法解析规范依旧不会得到想要的结果。不然的话,假设 import test_import.a,那是导入名为 test_import.a 的模块或包呢?还是导入 test_import 下的 a 呢?

也正如我们之前分析的 test_import.a,我们在导入 test_import.a 的时候,会把 test_import 加载进来,然后把 a 加到 test_import 的属性字典里面,最后只需要把 test_import 返回即可。

因为通过 test_import 可以找到 a,或者说 test_import.a 代表的含义就是从 test_import 的属性字典里面获取 a,所以 import test_import.a 必须要返回 test_import,而且只需返回 test_import。

至于 sys.modules 里面虽然存在字符串名为 "test_import.a"的 key 的,但这是为了避免重复加载所采取的策略,它依旧表示从 test_import 的属性字典里面获取 a。

import pandas.core

print(pandas.DataFrame({"a": [1, 2, 3]}))
"""
   a
0  1
1  2
2  3
"""
# 所以通过 pandas.DataFrame 是可以调用的

导入 pandas.core 会先导入 pandas,也就是执行 pandas 内部的 init 文件。虽然 sys.modules 里面同时有 "pandas" 和 "pandas.core",但是暴露在 local 空间的只有 pandas,所以调用 pandas.DataFrame 是完全合理的。至于 pandas.core 显然它无法暴露,因为这不符合 Python 的变量命名规范,变量的名称里面不能出现小数点,它只是单纯地表示从 pandas 的属性字典中加载 core。

from & import

from sklearn.linear_model import ridge
"""
  0 LOAD_CONST               0 (0)
  2 LOAD_CONST               1 (('ridge',))
  4 IMPORT_NAME              0 (sklearn.linear_model)
  6 IMPORT_FROM              1 (ridge)
  8 STORE_NAME               1 (ridge)
 10 POP_TOP
 12 LOAD_CONST               2 (None)
 14 RETURN_VALUE
"""

注意此时的 2 LOAD_CONST 不再是 None 了,而是一个元组,虚拟机将 ridge 放到了当前模块的 local 空间中。并且 sklearn.linear_model 和 sklearn 都被导入了,存在 sys.modules 里面。

但是 sklearn 却并不在当前 local 空间中,尽管它被创建了,但是又被隐藏了。IMPORT_NAME 是 sklearn.linear_model,也表示导入 sklearn,然后把 sklearn 下面的 linear_model 加入到 sklearn 的属性字典里面。

而之所以 sklearn 没在 local 空间里面,可以这样理解。当只出现 import 的时候,那么我们必须从头开始一级一级向下调用,所以顶层的包必须加入到 local 空间里面。但这里通过 from ... import ...把 ridge 导出了,此时 ridge 已经指向了 sklearn 下面的 linear_model 下面的 ridge,那么就不需要 sklearn 了,或者说 sklearn 就没必要暴露在 local 空间里面了,但它确实被导入进来了。

并且 sys.modules 里面也不存在 "ridge"这个key,存在的是 "sklearn.linear_model.ridge",暴露给 local空间的符号是 ridge。

所以正如上面所说,不管什么导入,都可以归结为 import x.y.z 的形式,只是暴露出来的符号不同罢了。

import & as

import sklearn.linear_model.ridge as xxx
"""
  0 LOAD_CONST               0 (0)
  2 LOAD_CONST               1 (None)
  4 IMPORT_NAME              0 (sklearn.linear_model.ridge)
  6 IMPORT_FROM              1 (linear_model)
  8 ROT_TWO
 10 POP_TOP
 12 IMPORT_FROM              2 (ridge)
 14 STORE_NAME               3 (xxx)
 16 POP_TOP
 18 LOAD_CONST               1 (None)
 20 RETURN_VALUE
""

这个和上面的 from & import 类似,"sklearn", "sklearn.linear_model", "sklearn.linear_model.ridge" 都在 sys.modules 里面。但是我们加上了 as xxx,那么这个 xxx 就直接指向了 sklearn 下面的 linear_model 下面的 ridge,此时就不需要 sklearn 了。

因此只有 xxx 暴露在了当前模块的 local空间里面,而 sklearn 虽然也被导入了,但它只在 sys.modules 里面,没有暴露给当前模块的 local 空间。

from & import & as

from sklearn.linear_model import ridge as xxx

这个我想连字节码都不需要贴了,和之前的 from & import 一样,只是最后暴露给 local 空间的 ridge 变成了我们自己指定的 xxx。

与module对象有关的名字空间问题

同函数、类一样,每个 PyModuleObject 也有自己的名字空间。一个模块不能直接访问另一个模块的内容,尽管模块内部的作用域比较复杂,比如:遵循 LEGB 规则,但是模块与模块之间的划分则是很明显的。

# test1.py
name = "古明地觉"
def print_name():
    return name
# test2.py
from test1 import name, print_name
name = "古明地恋"
print(print_name())  # 古明地觉

执行 test2.py 之后,发现打印的依旧是"古明地觉"。我们说 Python 是根据 LEGB 规则进行查找,而 print_name 函数里面没有 name,那么去外层找。test2.py 里面的 name 是"古明地恋",但是打印的依旧是 test1.py 里面的 "古明地觉"。为什么?

还是那句话,模块与模块之间的作用域划分的非常明显,print_name 是 test1.py 里面的函数,所以在返回 name 的时候,只会从 test1.py 中搜索,无论如何都是不会跳过test1.py、跑到 test2.py 里面的。

再来看个例子:

# test1.py
name = "古明地觉"
nicknames = ["小五", "少女觉"]
# test2.py
import test1
test1.name = "❤古明地觉❤"
test1.nicknames = ["觉大人"]
from test1 import name, nicknames
print(name)  # ❤古明地觉❤
print(nicknames)  # ['觉大人']

此时打印的结果变了,很简单,这里是直接把 test1 里面的变量修改了。因为这种方式,相当于直接修改 test1 的属性字典。那么后续再导入的时候,打印的就是修改之后的值。

# test1.py
name = "古明地觉"
nicknames = ["小五", "少女觉"]
# test2.py
from test1 import name, nicknames
name = "古明地恋"
nicknames.remove("小五")
from test1 import name, nicknames
print(name)  # 古明地觉
print(nicknames)  # ["少女觉"]

如果是 from test1 import name, nicknames,那么相当于在当前的 local空间中新创建变量 name 和 nicknames,它们和 test1 中的 name 和 nicknames 指向相同的对象。

name = "古明地觉" 相当于重新赋值了,所以不会影响test1里的 name;而 nicknames.remove 则是在本地进行修改,所以会产生影响。

小结

以上就是模块(包)相关的内容,虽然一个项目可以有很多个文件,但是每个文件的执行原理是一致的。无论一个文件是作为模块被导入,还是直接作为启动文件被执行,虚拟机的执行流程都没有变化。

通过模块和包,我们便可以对项目进行功能上的划分,从而更好地组织项目。

到此这篇关于python的import 机制是怎么实现的的文章就介绍到这了,更多相关import 机制内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: python的import 机制是怎么实现的

本文链接: https://www.lsjlt.com/news/117963.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • python的import 机制是怎么实现的
    目录import 机制功能单模块导入级联导入from & importimport & asfrom & import & as与module对象有关...
    99+
    2024-04-02
  • python的import机制如何实现
    本篇内容主要讲解“python的import机制如何实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“python的import机制如何实现”吧!import 机制功能Python 的 impo...
    99+
    2023-06-30
  • Python的import 机制中如何实现远程导入模块
    本篇文章为大家展示了Python的import 机制中如何实现远程导入模块,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。所谓的模块导入,是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用...
    99+
    2023-06-02
  • Python 的 import 是怎么工作的?
    你好,我是 somenzz,可以叫我征哥。Python 的 import 是非常直观的,但即使这样,有时候你会发现,明明包就在那里,我们仍会遇到 ModuleNotFoundError,明明相对路径非常正确,就是报错ImportError:...
    99+
    2023-05-15
    Python 模块 import
  • python中import和from-import的区别是什么
    本文小编为大家详细介绍“python中import和from-import的区别是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“python中import和from-import的区别是什么”文章能帮助大家解决疑惑,下面跟着小编的思路...
    99+
    2023-07-04
  • python import的本质是什么
    这篇文章主要讲解了“python import的本质是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python import的本质是什么”吧!说明import的本质是路径搜索。impo...
    99+
    2023-06-20
  • python中的垃圾回收机制怎么实现
    这篇文章主要讲解了“python中的垃圾回收机制怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“python中的垃圾回收机制怎么实现”吧!python采用的是引用计数机制为主,标记-清...
    99+
    2023-06-28
  • python中import的用法是什么
    在Python中,`import`关键字用于引入其他模块或者包。它可以将其他模块中的函数、变量、类等引入当前的命名空间,以便在当前模...
    99+
    2023-09-08
    python import
  • java实现多态的机制是什么
    Java实现多态的机制是通过方法的重写和方法的动态绑定来实现的。方法的重写是指在继承关系中,子类可以重写(覆盖)父类的方法,使得子类...
    99+
    2023-09-22
    java
  • linux管道的实现机制是什么
    今天小编给大家分享一下linux管道的实现机制是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解...
    99+
    2023-02-07
    linux
  • Ajax的内部实现机制是什么
    Ajax的内部实现机制主要涉及以下几个方面:1. XMLHttpRequest对象:Ajax通过XMLHttpRequest对象与服...
    99+
    2023-08-16
    Ajax
  • python的反射机制是怎样的
    python的反射机制是怎样的,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。反射反射机制就是在运行时,动态的确定对象的类型,并可以通过字符串调用对象属性、方法、导入模块,是...
    99+
    2023-06-02
  • python的广播机制是怎样的
    本篇文章为大家展示了python的广播机制是怎样的,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。为什么会有广播机制python语言在设计的时候,就就考虑到用于两个运算的矩阵或向量维度不匹配的问题。例...
    99+
    2023-06-22
  • python中from import的用法是什么
    在Python中,`from import`语句用于从模块中导入特定的函数、类或变量。它有以下几种使用方式:1. 导入模块中的特定函...
    99+
    2023-10-12
    python
  • 怎么实现localStorage的过期机制
    今天小编给大家分享一下怎么实现localStorage的过期机制的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。cookie过...
    99+
    2023-06-29
  • Mysql锁的内部实现机制是什么
    这篇“Mysql锁的内部实现机制是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Mys...
    99+
    2024-04-02
  • C++虚函数的实现机制是什么
    这篇文章主要讲解了“C++虚函数的实现机制是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++虚函数的实现机制是什么”吧!目录虚函数简介虚函数表简介有继承关系的虚函数表剖析1、单继承无...
    99+
    2023-06-20
  • python实现事务机制的方法
    这篇文章将为大家详细讲解有关python实现事务机制的方法,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。python的五大特点是什么python的五大特点:1.简单易学,开发程序时,专注的是解决问题,而不...
    99+
    2023-06-14
  • Python中import模块导入的实现原理
    欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起探讨和分享Linux C/C++/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入...
    99+
    2023-08-31
    python ai 机器学习 machine learning import 人工智能 ipython 原力计划
  • python中的import语句怎么用
    这篇文章给大家分享的是有关python中的import语句怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。格式一:import 模块名1 [as 别名1], 模块名2 [as 别名2],…说明:1)将整个模块...
    99+
    2023-06-20
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作