Python笔记03:python中用import导入包的机制原理是什么?
簡單地說,模塊就是一個保存了Python代碼的文件。模塊能定義函數,類和變量,模塊里也能包含可執行的代碼。使用模塊可以更加有邏輯地組織Python代碼段,使代碼更好用,更易懂。
為了組織好模塊,會將多個模塊分為包。Python 處理包也是相當方便的,簡單來說,包就是文件夾,但該文件夾下必須存在?__init__.py?文件。最簡單的情況下,init.py 為空文件即可,當然它也可以執行包的一些初始化代碼。
每個py文件被稱之為模塊,每個具有__init__.py文件的目錄被稱為包。只要模塊或者包所在的目錄在sys.path中,就可以使用import 模塊或import 包來使用。
在使用一個模塊中的函數或類之前,首先要導入該模塊。模塊的導入使用import語句,格式如下:
import module_name調用模塊的函數或類時,需要以模塊名作為前綴,如下:
module_name.func()如果不想在程序中使用模塊名前綴符,可以使用from import語句從模塊導入函數,如下:
from module_name import func func()上面的例子全部基于當前程序能夠找到 module_name 這個模塊的假設,下面先看幾個簡單的例子,對模塊導入有一個大致的認識,然后深入探究模塊導入的機制。
模塊導入示例
同級目錄下的調用,程序結構如下:
-- src|-- mod.py|-- test.py若想在模塊 test.py 中導入模塊 mod, 則使用下面語句即可:
import mod from mod import *調用子目錄下的模塊,程序結構如下:
-- src|-- lib| ? ?|-- mod.py|-- test.py如果想在模塊 test.py 中導入模塊mod.py,可以在lib件夾中建立空文件__init__.py文件,然后像下面這樣調用即可:
from lib.mod2 import * import lib.mod2調用上級目錄下的文件,程序結構如下:
-- src|-- mod1.py|-- lib| ? ?|-- mod2.py|-- sub| ? ?|-- test.py如果想在 test.py 中導入模塊 mod1和mod2,則先將目錄 src 加入到 sys.path,就可以導入mod1模塊。然后在lib中建一個空文件__init__.py,就可以導入lib下的mod2模塊,如下:
import sys sys.path.append("..") import mod1 import lib.mod2項目中具體如何設置模塊,如何設置導入,要考慮的問題比較多,具體可以參考極客學院的?模塊與包?這篇文章。
導入機制探究
Python 提供了 import 語句來實現類庫的引用,當我們執行一行?from package import module as mymodule?命令時,Python解釋器會查找package 這個包的module模塊,并將該模塊作為 mymodule 引入到當前的工作空間。所以import語句主要是做了二件事:
在import的第一個階段,主要是完成了查找要引入模塊的功能。查找時首先檢查 sys.modules (保存了之前import的類庫的緩存),如果module沒有被找到,則按照下面的搜索路徑查找模塊:
其大致過程可以簡化為:
def import(module_name):if module_name in sys.modules:return sys.modules[module_name]else:module_path = find(module_name)if module_path:module = load(module_path)sys.modules[module_name] = modulereturn moduleelse:raise ImportError模塊導入錯誤
在導入模塊方面,可能會出現下面的情況:
- 循環導入(circular imports)
- 覆蓋導入(Shadowed imports)
循環導入
如果你創建兩個模塊,二者相互導入對方,那么有可能會出現循環導入。例如創建 a.py如下:
import bdef a_test():print "in a_test"print b.xa_test()然后在同個文件夾中創建另一個模塊,將其命名為b.py。
import a x = 1def b_test():print 'In test_b'a.a_test()b_test()當我們導入 a 模塊時,會引發AttributeError,這是因為導入a時,在開始時導入 b 模塊,而 b 模塊調用 b_test 時需要a的 a_test,這時候 a 模塊的 a_test 并沒有成功加載。
注意不是所有的互相導入都會引發 AttributeError,官方文檔這樣說:
Circular imports are fine where both modules use the “import ” form of import. They fail when the 2nd module wants to grab a name out of the first (“from module import name”) and the import is at the top level. That’s because names in the 1st are not yet available, because the first module is busy importing the 2nd.
繼續以上面的兩個模塊為例,如果將 a.py 和 b.py 改為下面代碼,就不會出現循環導入的錯誤:
a.py
def a_test():import bprint "in a_test"print b.xa_test()b.py
x = 1def b_test():import aprint 'In test_b'a.a_test()b_test()這樣的話,先導入 a,再導入 b 的結果如下:
>>> import a In test_b in a_test 1 in a_test 1 >>> import b >>>覆蓋導入
當創建的模塊與標準庫中的模塊同名時,如果導入這個模塊,就會出現覆蓋導入。舉個例子,創建一個名叫math.py的文件,在其中寫入如下代碼:
import mathdef square_root(number):return math.sqrt(number)運行這個文件,你會得到以下信息(traceback):AttributeError: module 'math' has no attribute 'sqrt'。這是因為運行這個文件的時候,Python解釋器首先在當前運行腳本所處的的文件夾中查找名叫math的模塊。在這個例子中,解釋器找到了我們正在執行的模塊,試圖導入它。但是我們的模塊中并沒有叫sqrt的函數或屬性,所以就拋出了AttributeError。
==========================另一篇文章===================
?
模塊與包
在了解 import 之前,有兩個概念必須提一下:
- 模塊: 一個?.py?文件就是一個模塊(module)
- 包:?__init__.py?文件所在目錄就是包(package)
當然,這只是極簡版的概念。實際上包是一種特殊的模塊,而任何定義了?__path__?屬性的模塊都被當做包。只不過,咱們日常使用中并不需要知道這些。
兩種形式的 import
import?有兩種形式:
- import ...
- from ... import ...
兩者有著很細微的區別,先看幾行代碼。
| 1 2 3 | from string import ascii_lowercase import string import string.ascii_lowercase |
運行后發現最后一行代碼報錯:ImportError: No module named ascii_lowercase,意思是:“找不到叫 ascii_lowercase 的模塊”。第 1 行和第 3 行的區別只在于有沒有?from,翻翻語法定義發現有這樣的規則:
- import ...?后面只能是模塊或包
- from ... import ...?中,from?后面只能是模塊或包,import?后面可以是任何變量
可以簡單的記成:第一個空只能填模塊或包,第二個空填啥都行。
import 的搜索路徑
提問,下面這幾行代碼的輸出結果是多少?
| 1 2 3 | # foo.py import string print(string.ascii_lowercase) |
是小寫字母嗎?那可不一定,如果目錄樹是這樣的:
| 1 2 3 | ./ ├── foo.py └── string.py |
foo.py?所在目錄有叫?string.py?的文件,結果就不確定了。因為你不知道?import string?到底是 import 了?./string.py?還是標準庫的?string。為了回答這個問題,我們得了解一下 import 是怎么找到模塊的,這個過程比較簡單,只有兩個步驟:
而?sys.path?在初始化時,又會按照順序添加以下路徑:
import site?所添加的路徑一般是?XXX/site-packages(Ubuntu 上是?XXX/dist-packages),比如在我的機器上是?/usr/local/lib/python2.7/site-packages。同時,通過?pip?安裝的包也是保存在這個目錄下的。如果懶得記?sys.path?的初始化過程,可以簡單的認為 import 的查找順序是:
回到前面的問題,因為?import string?是通過搜尋?foo.py?文件所在目錄,找到?string.py?后 import 的,所以輸出取決于 import?string.py?時執行的代碼。
相對 import 與 絕對 import
相對 import
當項目規模變大,代碼復雜度上升的時候,我們通常會把一個一個的?.py?文件組織成一個包,讓項目結構更加清晰。這時候 import 又會出現一些問題,比如:一個典型包的目錄結構是這樣的:
| 1 2 3 4 | string/ ├── __init__.py ├── find.py └── foo.py |
如果?string/foo.py?的代碼如下:
| 1 2 3 | # string/foo.py from string import find print(find) |
那么?python string/foo.py?的運行結果會是下面的哪一個呢?
- <module 'string.find' from 'string/find.py'>
- <function find at 0x123456789>
按我們前面講的各種規則來推導,因為?foo.py?所在目錄?string/?沒有?string?模塊(即?string.py),所以 import 的是標準庫的?string,答案是后者。不過,如果你把?foo?當成?string?包中的模塊運行,即?python -m string.foo,會發現運行結果是前者。同樣的語句,卻有著兩種不同的語義,這無疑加重了咱們的心智負擔,總不能每次咱們調試包里的模塊時,都去檢查一下執行的命令是?python string/foo.py?還是?python -m string.foo?吧?
相對 import 就是專為解決「包內導入」(intra-package import)而出現的。它的使用也很簡單,from?的后面跟個?.?就行:
| 1 | from .XXX import ... |
比如:
| 1 2 3 4 | # from string/ import find.py from . import find # from string/find.py import * from .find import * |
我們再看個復雜點的例子,有個包的目錄結構長這樣:
| 123456789 10 | one/ ├── __init__.py ├── foo.py └── two/├── __init__.py├── bar.py└── three/├── __init__.py├── dull.py└── run.py |
foo.py、bar.py、dull.py?中的代碼分別是?print(1)、print(2)、print(3),并且?run.py?的代碼如下:
| 1 2 3 4 | from . import dull from .. import bar from ... import foo print('Go, go, go!') |
我們通過?python -m one.two.three.run?運行?run.py,可以看到?run.py?運行結果如下:
| 1 2 3 4 | 3 2 1 Go, go, go! |
意思就是,from?后面出現幾個?.?就表示往上找第幾層的包。也可以將?run.py?改寫成下面這樣,運行結果是一樣的:
| 1 2 3 4 | from .dull import * from ..bar import * from ...foo import * print('Go, go, go!') |
好啦,相對 import 就介紹到這里,回到最初的問題。如果用相對 import,把?string/foo.py?改寫成:
| 1 2 3 | # string/foo.py from . import find print(find) |
那么?python string/foo.py?和?python -m string.foo?的運行結果又是怎樣呢?運行一下發現,兩者的輸出分別是:
| 1 2 3 4 | Traceback (most recent call last):File "string/foo.py", line 1, in <module>from . import find ValueError: Attempted relative import in non-package |
| 1 | <module 'string.find' from 'string/find.py'> |
原因在于?python string/foo.py?把?foo.py?當成一個單獨的腳本來運行,認為?foo.py?不屬于任何包,所以此時相對 import 就會報錯。也就是說,無論命令行是怎么樣的,運行時 import 的語義都統一了,不會再出現運行結果不一致的情況。
絕對 import
絕對 import 和相對 import 很好區分,因為從行為上來看,絕對 import 會通過搜索?sys.path?來查找模塊;另一方面,除了相對 import 就只剩絕對 import 了嘛 :) 也就是說:
不過,第 2 點只對 2.7 及其以上的版本(包括 3.x)成立喔!如果是 2.7 以下的版本,得使用
| 1 | from __future__ import absolute_import |
兩者的差異
首先,絕對 import 是 Python 默認的 import 方式,其原因有兩點:
- 絕對 import 比相對 import 使用更頻繁
- 絕對 import 能實現相對 import 的所有功能
其次,兩者搜索模塊的方式不一樣:
- 對于相對 import,通過查看?__name__?變量,在「包層級」(package hierarchy)中搜索
- 對于絕對 import,當不處于包層級中時,搜索?sys.path
前面在介紹?sys.path?的初始化的時候,我在有個地方故意模棱兩可,即:
foo.py 所在目錄(如果是軟鏈接,那么是真正的 foo.py 所在目錄)或 當前目錄
官方文檔的原文是:
the directory containing the input script (or the current directory).
這是因為當模塊處于包層級中的時候,絕對 import 的行為比較蛋疼,官方的說法是:
The submodules often need to refer to each other. For example, the surround module might use the echo module. In fact, such references are so common that the import statement first looks in the containing package before looking in the standard module search path. Thus, the surround module can simply use import echo or from echo import echofilter. If the imported module is not found in the current package (the package of which the current module is a submodule), the import statement looks for a top-level module with the given name.
但是在我的測試中發現,其行為可能是下面兩者中的任意一種:
- .py?文件所在目錄
- 當前目錄
比如,對于目錄結構如下的包:
| 1 2 3 4 5 6 7 8 | father/ ├── __init__.py ├── child/ │?? ├── __init__.py │?? ├── foo.py │?? └── string.py └── string/└── __init__.py |
其中,foo.py?代碼如下:
| 1 2 | import string print(string) |
import string?真正導入的模塊是:
| 2.7.11 | child/string.py | child/string.py |
| 3.5.1 | string/__init__.py | child/string.py |
如果將?foo.py?的代碼改成(你可以?print(sys.path)?看看為什么改成這樣):
| 1 2 3 4 | import sys sys.path[0] = '' import string print(string) |
import 的模塊就變成了:
| 2.7.11 | child/string.py | string/__init__.py |
| 3.5.1 | string/__init__.py | string/__init__.py |
為了避免踩到這種坑,咱們可以這樣子:
- 避免包或模塊重名,避免使用?__main__.py
- 包內引用盡量使用相對 import
import 的大致過程
import 的實際過程十分復雜,不過其大致過程可以簡化為:
| 123456789 10 11 12 | def import(module_name):if module_name in sys.modules:return sys.modules[module_name]else:module_path = find(module_name)if module_path:module = load(module_path)sys.modules[module_name] = modulereturn moduleelse:raise ImportError |
sys.modules?用于緩存,避免重復 import 帶來的開銷;load?會將模塊執行一次,類似于直接運行。
Tips
- import 會生成?.pyc?文件,.pyc?文件的執行速度不比?.py?快,但是加載速度更快
- 重復 import 只會執行第一次 import
- 如果在?ipython?中 import 的模塊發生改動,需要通過?reload?函數重新加載
- import *?會導入除了以?_?開頭的所有變量,但是如果定義了?__all__,那么會導入?__all__?中列出的東西
參考資料:
https://loggerhead.me/posts/python-de-import-ji-zhi.html
https://github.com/xuelangZF/CS_Offer/blob/master/Python/Package.md
?
總結
以上是生活随笔為你收集整理的Python笔记03:python中用import导入包的机制原理是什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google Cloud Platfor
- 下一篇: python中import re_彻底搞