python中经常用到模块,比如import xxx,from xxx import yyy这样子,里面的机制也是需要好好探究一下的,这次主要从黑盒角度来探测模块机制,源码分析点到为止,详尽的源码分析见陈儒大神的《python源码剖析》第14章。

1 如何导入模块

首先来看一个导入模块的例子。创建一个文件夹demo5,文件夹中有如下几个文件。

ssj@ssj-mbp ~/demo5 $ ls

__init__.py math.py sys.py test.py

根据python规则,因为文件夹下面有init.py文件,因此demo5是一个包。各个文件内容如下:

#__init__.py

import sys

import math

#math.py

print 'my math'

#sys.py

print 'my sys'

#test.py

import sys

import math

好了,问题来了,当我在demo5目录运行python test.py的时候,会打印什么结果呢?sys模块和math模块会调用demo5目录下面的还是系统本身的模块呢?结果是只打印出了my math,也就是说,sys模块并不是导入的demo5目录下面的sys模块。但是,如果我们不是直接运行test.py,而是导入整个包呢?结果大为不同,当我们在demo5上层目录执行import demo5时,可以发现打印出了my sys和my math,也就是说,导入的都是demo5目录下面的两个模块。出现这两个不同结果就是python模块和包导入机制导致的。下面来分析下python模块和包导入机制。

2 Python模块和包导入原理

python模块和包导入函数调用路径是builtin___import__->import_module_level->load_next->import_submodule->find_module->load_module,本文不打算分析所有的函数,只摘出几处关键代码分析。

builtin___import__函数解析import参数,比如import xxx和from yyy import xxx解析后获取的参数是不一样的。然后通过import_module_level函数解析模块和包的树状结构,并调用load_next来导入模块。而load_next调用import_submodule来查找并导入模块。注意到如果是从包里面导入模块的话,load_next会先用包含包名的完整模块名调用import_submodule来寻找并导入模块,如果找不到,则只用模块名来寻找并导入模块。import_submodule会先根据模块完整名fullname来判断是否是系统模块,即之前说过的sys.modules是否有该模块,比如sys,os等模块,如果是系统模块,则直接返回对应模块。否则根据模块路径调用find_module搜索模块并调用load_module函数导入模块。注意到如果不是从包中导入模块,find_module中会判断模块是否是内置模块或者扩展模块(注意到这里的内置模块和扩展模块是指不常用的系统模块,比如imp和math模块等),如果是则直接初始化该内置模块并加入到之前的备份模块集合extensions中。否则需要先后搜索模块包的路径和系统默认路径是否有该模块,如果都没有搜索到该模块,则报错。找到了模块,则初始化模块并将模块引用加入到sys.modules中。

load_module这个函数需要额外说明下,该函数会根据模块类型不同来使用不同的加载方式,基本类型有PY_SOURCE, PY_COMPILED,C_BUILTIN, C_EXTENSION,PKG_DIRECTORY等。PY_SOURCE指的就是普通的py文件,而PY_COMPILED则是指编译后的pyc文件,如果py文件和pyc文件都存在,则这里的类型为PY_SOURCE,你可能会有点纳闷了,这样岂不是影响效率了么?其实不然,这是为了保证导入的是最新的模块代码,因为在load_source_module中会判断pyc文件是否过时,如果没有过时,还是会在这里导入pyc文件的,所以性能上并不会有太多影响。而C_BUILTIN指的是系统内置模块,比如imp模块,C_EXTENSION指的是扩展模块,通常是以动态链接库形式存在的,比如math.so模块。PKG_DIRECTORY则是指导入的是包,比如导入demo5包,会先导入包demo5本身,然后导入init.py模块。

/*load_next函数部分代码*/

static PyObject *load_next() {

.......

result = import_submodule(mod, p, buf); //p是模块名,buf是包含包名的完整模块名

if (result == Py_None && altmod != mod) {

result = import_submodule(altmod, p, p);

}

.......

}

/*import_submodule部分代码*/

static PyObject *

import_submodule(PyObject *mod, char *subname, char *fullname)

{

PyObject *modules = PyImport_GetModuleDict();

PyObject *m = NULL;

if ((m = PyDict_GetItemString(modules, fullname)) != NULL) {

Py_INCREF(m);

}

else {

......

if (mod == Py_None)

path = NULL;

else {

path = PyObject_GetAttrString(mod, "__path__");

......

}

.......

fdp = find_module(fullname, subname, path, buf, MAXPATHLEN+1,

&fp, &loader);

.......

m = load_module(fullname, fp, buf, fdp->type, loader);

.......

if (!add_submodule(mod, m, fullname, subname, modules)) {

Py_XDECREF(m);

m = NULL;

}

}

return m;

}

接下来就需要解释下第一节中提出的问题了,首先直接python test.py的时候,那么先后导入sys模块和math模块,由于是直接导入模块,则全名就是sys,在导入sys模块的时候,虽然当前目录下有sys模块,但是sys模块是系统模块,所以会在import_submodule中直接返回系统的sys模块。而math模块不是系统预先加载的模块,所以会在当前目录下找到并加载。

而如果使用了包机制,我们import demo5时,则此时会先加载demo5包本身,然后加载__init__.py模块,init.py中会加载sys和math模块,由于是通过包来加载,所以fullname会变成demo5.sys和demo5.math。显然在判断的时候,demo5.sys不在系统预先加载的模块sys.modules中,因此最终会加载当前目录下面的sys模块。math则跟前面情况类似。

3 模块和名字空间

在导入模块的时候,会在名字空间中引入对应的名字。注意到导入模块和设置的名字空间的名字时不一样的,需要注意区分下。下面给个栗子,这里有个包foobar,里面有a.py, b.py,__init__.py。

In [1]: import sys

In [2]: sys.modules['foobar']

---------------------------------------------------------------------------

KeyError Traceback (most recent call last)

in ()

----> 1 sys.modules['foobar']

KeyError: 'foobar'

In [3]: import foobar

import package foobar

In [4]: sys.modules['foobar']

Out[4]:

In [5]: import foobar.a

import module a

In [6]: sys.modules['foobar.a']

Out[6]:

In [7]: locals()['foobar']

Out[7]:

In [8]: locals()['foobar.a']

---------------------------------------------------------------------------

KeyError Traceback (most recent call last)

in ()

----> 1 locals()['foobar.a']

KeyError: 'foobar.a'

In [9]: from foobar import b

import module b

In [10]: locals()['b']

Out[10]:

In [11]: sys.modules['foobar.b']

Out[11]:

In [12]: sys.modules['b']

---------------------------------------------------------------------------

KeyError Traceback (most recent call last)

in ()

----> 1 sys.modules['b']

KeyError: 'b'

我们知道,导入的模块都会加入到sys.modules字典中。当我们导入模块的时候,可以简单分为以下几种情况,具体原理可以参见源码:

import foobar.a

这是直接导入模块a,那么在sys.modules中存在foobar和foobar.a,但是在local名字空间中只存在foobar,并没有foobar.a。这是由import机制决定的,在导入模块的代码中可以看到针对foobar.a最终存储到名字空间的只有foobar。

from foobar import b

这种情况存储到sys.modules的也只有foobar(前面已经导入不会重复导入了)和foobar.b。local名字空间只有b,没有foobar,也没有foobar.b。

import foobar.a as A

这种情况sys.modules中还是foobar和foobar.a,而local名字空间只有A,没有foobar,更没有foobar.a。如果我们执行del A删除符号A,则名字空间不在有符号A,但是在sys.modules中还是存在foobar和foobar.a的。

4 其他

需要提到的一点是,如果我们修改了某个py文件,然后reload该模块,则删除的符号并不会更新,而只是会加入新增加的符号或者更新已经有的符号。比如下面这个例子,我们加入 b = 2后reload模块reloadtest,可以看到模块中多了符号b,而我们删除b = 2加入c=3后,发现符号b还是在模块reloadtest中,并没有删除。这是python内部reload机制决定的,在reload操作时,python内部实现是找到原模块的字典,并更新或添加符号,并不删除原有的符号。

#初始代码 a = 1

In [1]: import reloadtest

In [2]: import sys

In [3]: dir(sys.modules['reloadtest'])

Out[3]: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a']

##新增一行代码 b = 2

In [4]: reload(reloadtest)

Out[4]:

In [5]: dir(sys.modules['reloadtest'])

Out[5]: ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b']

##删除代码 b = 2,新增 c = 3

In [6]: reload(reloadtest)

Out[6]:

In [7]: dir(sys.modules['reloadtest'])

Out[7]:

['__builtins__',

'__doc__',

'__file__',

'__name__',

'__package__',

'a',

'b',

'c']

python源码剖析代码例子_Python源码剖析笔记5-模块机制相关推荐

  1. python字符串代码对象_Python源码剖析 - Python中的字符串对象

    1. 前言 我们已经在 [Python中的整数对象] 章节中对定长对象进行了详细的讲解,接下来我们将介绍变长对象,而字符串类型,则是这类对象的典型代表. 这里必须先引入一个概念: Python 中的变 ...

  2. python程序代码解析_Python源码分析3 – 词法分析器PyTokenizer

    Introduction 上次我们分析了Python中执行程序可分为5个步骤: Tokenizer进行词法分析,把源程序分解为Token Parser根据Token创建CST CST被转换为AST A ...

  3. python分析人口出生率代码_【源码】国家统计局人口数据采集Python脚本数据汇总 | 州的先生...

    在上一篇文章<"一胎化"35年,Python可视化初探中国人口变化>中,州的先生(微信号:taoist_ling,公众号:zmister2016)通过采集国家统计局&q ...

  4. python条件语句代码例子_Python 炫技操作:条件语句的七种写法

    原标题:Python 炫技操作:条件语句的七种写法 作者 | 写代码的明哥 责编 | 郭芮 有的人说 Python 入门容易,但是精通难的语言,这点我非常赞同. Python 语言里有许多(而且是越来 ...

  5. python以什么表示代码层次_python 中几个层次的中文编码.md

    介绍 一直不太喜欢使用命令行,所以去年年底的技术创新中,使用TkInter来开发小工具.结果花费了大量的时间来学习TkInter ui的使用. 最近想整理该工具,使用命令行的形式,然后将该工具做成ex ...

  6. python画折线图代码实现_python如何绘制分布折线图 python绘制分布折线图代码示例...

    python如何绘制分布折线图?本篇文章小编给大家分享一下python绘制分布折线图代码示例,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看. 用Pyth ...

  7. python对象的生命周期_(python)通过一个代码例子来分析对象的生命周期 | 学步园...

    最近在学习python,看的是<简明python教程>,写的很是通俗易懂. 在一个类和对象的变量的例子中,看到代码运行结果突然想到在python中对象的生命周期的问题,代码运行结果: #! ...

  8. python对象的生命周期_(python)通过一个代码例子来分析对象的生命周期

    最近在学习python,看的是<简明python教程>,写的很是通俗易懂. 在一个类和对象的变量的例子中,看到代码运行结果突然想到在python中对象的生命周期的问题,代码运行结果: #! ...

  9. python函数能否增强代码可读性_python——初识函数

    一.初识函数定义与调用 #函数定义 defmylen():"""计算s1的长度"""s1= "hello world"l ...

最新文章

  1. 【C++】43.使用【类对象】与 【类指针】的区别
  2. NetBeans可用性提示
  3. slope one matlab代码,经典推荐算法之 Slope one
  4. python爬虫爬取大众点评并导入redis
  5. 年底了,你总结了吗?我先来。
  6. c语言公共基础知识占多少分,计算机二级公共基础知识多少分
  7. 胡耀文教你:裂变8级、转化率32%、K值7.4的老带新式分销全复盘
  8. 20180710使用gh
  9. 踏进字节的那一瞬间,我泪目了,这457天的外包经历值了....
  10. R或RStudio下载包时出错解决方案
  11. 数理统计(数值修约、0.5修约、0.2修约、有效数字运算、平均值、中位数、极差、标准差、变异系数)
  12. org.apache.poi Excel列与行都是动态生成的_网络爬虫:Python动态网页爬虫2种技术方式及示例...
  13. Maven - 国内Maven仓库之阿里云Aliyun仓库地址及设置
  14. Android 锁定屏幕方向 横向或竖向 支持Android10
  15. Dockerflie概述
  16. bigdata学习笔记--01 Linux基础--Linux目录结构
  17. JavaScript大体学习思路
  18. linux服务器防病毒,Linux服务器防病毒实战(3)
  19. 文字图片滚动scrollbox插件
  20. 台式机linux系统无线上网,Linux安装无线网卡驱动,实现Linux无线上网

热门文章

  1. 前端根据不同的值赋予渐变色
  2. 江西哪所高校计算机最强,江西最好考的2所大学!计算机、财务专业省排名第三,力压一本!...
  3. 浅析Google云计算
  4. 此Slashdotcn模仿彼Slashdot
  5. oracle时间判断上下午,oracle查询时间的判断
  6. 35岁的前端开发工程师,未来路在何方?
  7. 华南理工大学计算机考研难不难,华南理工大学考研难吗?一般要什么水平才可以进入?...
  8. CAD画图教程:CAD怎么画一条指定长度的线?
  9. python二手交易平台代码_使用Python探索二手车市场(含代码)
  10. Markdown语言基础使用教程