《Python 源码剖析》笔记
import
在交互环境下,使用不带参数的dir()
可以打印当前local
命名空间的所有键
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None}
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']
通过import
向local
添加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
依然加载了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']
<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 只是简单地将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 "<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']
网友评论