python加载模块_Python 模块加载

《Python 源码剖析》笔记

import

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

>>> locals()

{'__builtins__': , '__name__': '__main__', '__doc__': None, '__package__': None}

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__']

通过import向local添加sys模块

>>> import sys

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__', 'sys']

>>> locals()

{'__builtins__': , '__name__': '__main__', 'sys': , '__doc__': None, '__package__': None}

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

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

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

>>> sys.modules

{'sys': , 'os.path': , 'os': }

>>> os

Traceback (most recent call last):

File "", line 1, in

NameError: name 'os' is not defined

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

>>> import os

>>> os

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__', 'os', 'sys']

>>> locals()

{'__builtins__': , '__package__': None, 'sys': , '__name__': '__main__', 'os': , '__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 "", line 1, in

KeyError: 'hello'

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__', 'os', 'sys']

>>> import hello

>>> sys.modules['hello']

>>> 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 "", line 1, in

NameError: name 'hi2' is not defined

>>> dir(hi1.hi2)

['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'sys']

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

>>> sys.modules['hi1']

>>> sys.modules['hi2']

>>> sys.modules['sys']

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 "", line 1, in

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']

>>> sys.modules['tank1']

Traceback (most recent call last):

File "", line 1, in

KeyError: 'tank1'

>>> sys.modules['task.tank1']

可以看到,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']

>>> sys.modules['task.tank1']

可以看到,虽然local中只有tank1,但sys.modules依然加载了task和task.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']

>>> sys.modules['tank1']

Traceback (most recent call last):

File "", line 1, in

KeyError: 'tank1'

>>> sys.modules['task.tank1']

>>> sys.modules['task.tank1.a']

Traceback (most recent call last):

File "", line 1, in

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 "", line 1, in

KeyError: 'test'

>>> sys.modules['task.tank1']

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

模块的销毁

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

>>> del test

>>> dir()

['__builtins__', '__doc__', '__name__', '__package__', 'sys']

>>> sys.modules['task.tank1']

可以看出,del 只是简单地将test从local中删除,并没有从 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 "", line 1

reload test

^

SyntaxError: invalid syntax

>>> reload(test)

>>> dir(test)

['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'b', 'c']

>>> test.a

1

>>> test.b

1

>>> test.c

3

>>> id(test)

4300178104

>>> reload(test)

>>> 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)

>>> dir(test)

['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'c']

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值