Python 模块加载

《Python 源码剖析》笔记

import

在交互环境下,使用不带参数的dir()可以打印当前local命名空间的所有

>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None}
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']

通过importlocal添加sys模块

>>> import sys
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 'sys': <module 'sys' (built-in)>, '__doc__': None, '__package__': None}

可以看到,import机制使得加载的模块在local命名空间可见

在 Python 初始化时,就有大批 module 已经加载到内存中,但为了保持local空间的干净,并没有直接将这些 module 放入local空间,而是让用户通过import机制通知 Python:我的程序需要调用某个模块,请将它放入local命名空间。

那些被预加载到内存的模块,存放在 全局module集合sys.modules中(下面列出了部分)。此时虽然sys.modules中能看到os,但调用失败,因为还没有放入local空间

>>> sys.modules
{'sys': <module 'sys' (built-in)>, 'os.path': <module 'posixpath' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>, 'os': <module 'os' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>}
>>> os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined

通过import os,将模块放入local空间,用于调用

>>> import os
>>> os
<module 'os' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'os', 'sys']
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'sys': <module 'sys' (built-in)>, '__name__': '__main__', 'os': <module 'os' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>, '__doc__': None}

那么,对于那些一开始就并不在sys.modules中的模块,比如用户自定义的模块,又会如何呢?

准备了一个简单的module——hello.py:

a = 1
b = 2

hello.py进行import操作,结果,将'hello'加载进了sys.modules,同时放入了local空间

>>> sys.modules['hello']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'hello'
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'os', 'sys']
>>> import hello
>>> sys.modules['hello']
<module 'hello' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hello.py'>
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'hello', 'os', 'sys']

通过id模块,查看这两个对象指向了同一个内存地址空间,是同一个模块对象。

>>> id(hello)
4300177880
>>> id(sys.modules['hello'])
4300177880

可以看到,module对象其实就是通过一个dict维护所有的属性和值。也就是说,是一个名字空间。

>>> dir(hello)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b']
>>> hello.__dict__.keys()
['a', 'b', '__builtins__', '__file__', '__package__', '__name__', '__doc__']
>>> hello.__name__
'hello'
>>> hello.__file__
'/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hello.py'
>>> hello.__package__
>>> hello.__doc__
>>> hello.a
1
>>> hello.b
2

嵌套import

那么,对于嵌套的module呢?

hi1.py

import hi2

hi2.py

import sys

结果可以看到,在local空间中也是嵌套的

>>> import hi1
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'hello', 'hi1', 'os', 'sys']
>>> dir(hi1)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'hi2']
>>> dir(hi2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'hi2' is not defined
>>> dir(hi1.hi2)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'sys']

这些模块也同时出现在了sys.modules中,这样,当其他地方也要调用这些模块时,可以直接返回其中的 module对象。

>>> sys.modules['hi1']
<module 'hi1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hi1.py'>
>>> sys.modules['hi2']
<module 'hi2' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/hi2.py'>
>>> sys.modules['sys']
<module 'sys' (built-in)>

import package

如同一些相关的 class 可以放入一个 module 中,相关的 module 也可以放入一个 package,用于管理 module。当然,多个小 package 也可以组成一个较大的 package。

➜  task  pwd
/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task
➜  task  ls
tank1.py tank2.py
>>> import task
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named task

package 就是一个文件夹,但并非所有文件夹都是package,只有在文件夹中有一个特殊文件__init__.py时,Python 才会认为是合法的,即使这个文件是空的。

➜  task  ls
__init__.py tank1.py    tank2.py

再次导入 package,自动加载执行__init__.py,生成字节码__init__.pyc

>>> import task

➜  task  ls
__init__.py  __init__.pyc tank1.py     tank2.py

导入 package 中的 module

>>> import task.tank1
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', '__warningregistry__', 'hello', 'hi1', 'os', 'sys', 'task']
>>> dir(task)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'tank1']
>>> sys.modules['task']
<module 'task' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/__init__.py'>
>>> sys.modules['tank1']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'tank1'
>>> sys.modules['task.tank1']
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.py'>

可以看到,module 和 package 的导入基本一样。只不过包本身也被加载进来,而且在sys.modules中,模块名称中包含包名,这是为了区别不同包中的同名模块,以便共存。

为什么task也会被加载呢,似乎用户并不需要?这是因为对tank1的引用需要通过task.tank1实现,Python 会首先在local空间中查找task,然后在task的属性集合中查找tank1

import task.tank1时,只会导入tank1,不会同时导入tank2

而且,packagetask只会加载一次。

在导入类似A.B.C的结构时,Python 会视为树形结构,B在A下,C在B下,Python 会对这个结构进行分解,形成(A,B,C)的节点集合。从左到右依次到sys.modules中查找是否已经加载,如果是一个 package,A已经加载,但 B 和 C 还没有,就到 A 对应的 模块对象 的__path__中查找路径,并只在路径中搜索。

from import

我们可以只将 package 中的某个 module 甚至 某个 module 中的某个符号加载到内存,比如上例中,我们希望直接将tank1引入local空间,不需要引入task

>>> import sys
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>> from task import tank1
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'tank1']
>>> sys.modules['task']
<module 'task' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/__init__.pyc'>
>>> sys.modules['task.tank1']
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>

可以看到,虽然local中只有tank1,但sys.modules依然加载了tasktask.tank1,所以,import 和 from import 本质是一样的,需要将 包 和 模块 同时加载到sys.modules,区别只是加载完成后,将什么名称放入local空间。

import task.tank1中,引入了task并映射到module task,而在from task import tank1中,引入了tank1并映射到module task.tank1

可以只引入模块中的一些对象

tank1.py

a = 1

b = 2
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>> from task.tank1 import a
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a', 'sys']
>>> a
1
>>> sys.modules['task']
<module 'task' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/__init__.pyc'>
>>> sys.modules['tank1']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'tank1'
>>> sys.modules['task.tank1']
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.py'>
>>> sys.modules['task.tank1.a']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'task.tank1.a'

也可以一次性导入 module 中的所有对象

>>> from task.tank1 import *
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a', 'b', 'sys']

import as & from import as

Python 还允许自己重命名被引入local的模块

>>> import task.tank1 as test
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'test']
>>> sys.modules['test']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'test'
>>> sys.modules['task.tank1']
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>

这时,test映射到了module task.tank1

模块的销毁

如果一个模块不需要了,可以通过del销毁,但这样这的是销毁了这个模块对象么?

>>> del test
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys']
>>> sys.modules['task.tank1']
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>

可以看出,del 只是简单地将testlocal中删除,并没有从 module集合 中销毁。但这样已经能够让 Python程序无法访问这个模块,认为test不存在了。

为什么 Python 不直接将模块从sys.modules删除?因为一个系统的多个Python文件可能都会对某个module进行 import,而 import 的作用并非一直以为的加载,而是让某个module能够被感知,即以某种符号的形式引入某个名字空间。所以,使用全局的sys.modules保存module的唯一映射,如果某个Python文件希望感知到,而sys.modules中已经加载,就将这个模块名称引入该Python文件的名称空间,然后关联到sys.modules中的该模块,如果sys.modules中不存在,才会进行加载。

模块重新载入

那么,对于sys.modules中已经加载到内存的模块,如果后来对内容进行了修改,怎么让Python知道呢?有一种重新加载的机制。

>>> import task.tank1 as test
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'sys', 'test']
>>> dir(test)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b']
>>> test.a
1
>>> test.b
1

tank1.py 添加 整数对象 c

a = 1

b = 1

c = 3

重新加载,C出现了

>>> reload test
  File "<stdin>", line 1
    reload test
              ^
SyntaxError: invalid syntax
>>> reload(test)
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.py'>
>>> dir(test)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b', 'c']
>>> test.a
1
>>> test.b
1
>>> test.c
3
>>> id(test)
4300178104
>>> reload(test)
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>
>>> id(test)
4300178104

id(test)可以看出,依然是原来的对象,Python只是在原有对象中添加了C和对应的值。

但是,删除b = 1后,还可以调用,说明Python的重新加载只是向module添加新对象,而不管是否已经删除。

可以通过用del直接删除的方式进行更新

>>> del test.b
>>> dir(test)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'c']
>>> reload(test)
<module 'task.tank1' from '/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/task/tank1.pyc'>
>>> dir(test)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'c']
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容