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']
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容