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



作者:超net
鏈接:https://www.jianshu.com/p/c04dc172335e
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章