用實例來說明 import
的作用吧。
創建以下包結構。一個文件夾 cookFish/
,下面包含兩個文件, __init__.py
和cookBook.py
。
為什么取這幾個名字呢?假設我想用 Python 去做和魚相關的菜,這件事情很復雜,所以我給它創建了一個包,名叫cookFish
, 既然是包,在它下面必須得創建一個文件__init__.py
。燒魚必備條件之一就是菜譜,所以接着創建了 cookBook.py
。這幾個文件對我們這次來說就足夠了,所以就沒有再創建其他文件了。
cookFish/
__init__.py
cookBook.py
在cookFish/__init__.py
中輸入如下代碼:
__version__ = '0.1'
__author__ = 'XIE Byron'
def cookFish_hello():
print("cookFish_Hello() from cookFish/__init__.py")
在cookFish/cookBook.py
中輸入如下代碼:
def cookBook_hello():
print("cookBook_hello() from cookBook.py")
提示:下面的實例都是在 Python 自帶的命令行解釋器(windows+python 3.7)中運行的結果。如果你在其他環境下運行,比如
jupyter notebook
,輸出會有差異。
"import package-name" 都做了什么?
導入包cookFish
。
>>> import cookFish
提示:
如果
import
時出現錯誤ModuleNotFoundError
,如下:>>> import cookFish Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'cookFish'
建議先將 Python 的當前工作目錄設置為
cookFish
的 父文件夾(就是包含cookFish
文件夾的文件夾)。命令如下:>>> import os >>> os.chdir(r'path\to\parent\folder\of\cookFish')
用dir
操作查看當前命名空間和cookFish
命名空間下都有哪些內容。
>>> dir() # 查看當前命名空間下的對象。注意: cookFish 在當前命名空間下。
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cookFish', 'os']
>>> dir(cookFish) # 查看 cookFish 命名空間下的對象。
['__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'cookFish_hello']
其中的的 __author__
, __version__
, cookFish_hello
是我們定義的,都導入到了 cookFish
的命名空間下。但是cookFish
下的模塊 cookBook.py
沒有被導入。這是因為直接 import cookFish
只運行cookFish
文件夾下的 __init__.py
文件,不會運行其他模塊,所以cookBook
沒有被導入。
提示:Python 中的
模塊
指后綴.py
的文件,也叫腳本
。包
指包含__init__.py
文件的一個文件夾,一般還會包含其他模塊。
包/模塊的命名空間
這里講一下我對概念“在cookFish
的命名空間下”的理解。
Python 的 import A
會把 A
的Python 代碼運行一遍,並把運行結果放在一個叫A
的命名空間下。
提示: 如果
A
是包,A
的 Python 代碼就是 文件夾A下的__init__.py
中的代碼。 如果A
是模塊,那么就是文件A.py
中的代碼。
import B
會把 B
的 Python 代碼運行一遍,並把運行結果放在一個叫 B
的命名空間下。假設A
和B
中都有一個叫X
的對象, A
中的X
在當前命名空間下叫 A.X
,B
中的X
在當前命名空間下叫 B.X
,兩個X
在當前命名空間下不重名。
提示: 這里的
對象
指 Python 中的變量/屬性,函數,類,實例等等。
比如__version__
屬性(或者叫它變量)就在cookFish
的命名空間下,我們只能通過 cookFish.__version__
才能訪問到 __version__
,直接輸入 __version__
訪問不到它,會報錯。
直接輸入__version__
運行會報如下錯誤:
>>> __version__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '__version__' is not defined
其他導入包/模塊的方式
如果我們想導入 cookFish
下的模塊 cookBook
呢?可以用下面的語法:
>>> import cookFish.cookBook
然后在 cookFish
的命名空間下又多了 cookBook
。
>>> dir(cookFish)
['__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'cookBook', 'cookFish_hello']
然后就能通過全名cookFish.cookBook
訪問cookBook.py
中的對象了,比如:
>>> cookFish.cookBook.cookBook_hello()
cookBook_hello() from cookBook.py
好長的名字啊,能不能短一點啊?當然可以:
>>> import cookFish.cookBook as cb
然后在當前命名空間下就多了對象 cb
:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cb', 'cookFish', 'os']
然后就能通過別名cb
來訪問cookBook.py
中的對象了,比如:
>>> cb.cookBook_hello()
cookBook_hello() from cookBook.py
那我能不能只導入cookBook_hello()
到當前命名空間?當然可以
>>> from cookFish.cookBook import cookBook_hello
然后 cookBook_hello
就被導入到當前命名空間下了:
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cb', 'cookBook_hello', 'cookFish', 'os']
然后就能直接訪問 cookBook_hello()
了,不用任何前綴:
>>> cookBook_hello()
cookBook_hello() from cookBook.py
“from 包/模塊名 import *” 是導入所有對象嗎?
那我可以一次性導入 cookFish
下的所有模塊、所有包嗎?可以也不可以。
Python 有一個條指令
from 包/模塊名 import *
比如from cookFish import *
,給我們的第一感覺是,這條指令是遍歷了 cookFish
下的所有文件,找到這個包下面的所有包和模塊,把他們統統導入到當前命名空間。
但不幸的是,這個操作在windows和Mac系統上不能很好地實現。因為它們的文件系統不能提供准確的文件名大小寫信息。在這兩個平台上,Python 不知道應該把ECHO.py
導入為模塊echo
, Echo
還是ECHO
,或者其他。(比如windows 95 上面,所有文件名的首字母都會顯示為大寫)。如果Python 把 ECHO.py
導入為 模塊Echo
,但實際Python代碼中有時按照 echo
使用的,那肯定會報錯。[1]
Python 支持大小寫,
Echo
和ECHO
是兩個不一樣的對象
Python 的唯一的解決辦法是包的作者提供一個明確的包的索引,告訴 Python 在 Python 代碼中如何命名這個模塊。import 語句定義下面一個約定,如果在包的 __init__.py
中定義了一個 __all__
列表,在 from xxx import *
時,Python 就會把 __all__
列表中的對象導入。
! 注意:
__all__
只對from xxx import *
有影響,對其他 import 操作沒有任何影響
在cookFish/__init__.py
中, 我們只把函數 cookFish_hello
加入__all__
中,代碼如下:
__all__ = ['cookFish_hello', ] # added to support `from xxx import *`
__version__ = '0.1'
__author__ = 'XIE Byron'
def cookFish_hello():
print("cookFish_Hello() from cookFish/__init__.py")
重啟 Python 解釋器,在導入之前,先運行 dir()
顯示當前命名空間的對象。
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'os']
! 注意:
Python 解釋器為了提高運行效率,同一個模塊只會導入一次。一個模塊被導入后,再次運行導入命名不會重新導入。為了顯示
from xx import *
的特殊性,所以需要重啟 Python 解釋器(就是關閉 Python 解釋器,然后重新進入)。
然后運行如下:
>>> from cookFish import *
然后輸入 dir()
查看 cookFish_Hello()
是否被導入到了當前命名空間.
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'cookFish_hello', 'os']
可以看到只有在__all__
列表中的 cookFish_hello
被導入到當前命名空間,其他什么都沒有導入,連cookFish
本身也沒有被導入。
所以問題“可以一次性導入 cookFish
下的所有模塊、所有包嗎?“ 的答案是:是否能一次導入,取決於包的作者有沒有把所有子模塊/子包都加入到 __all__
列表中。
參考
[1] Built-in Package Support in Python 1.5
版本
[1] version 1.0, released on 2019-04-21
[2] version 1.1, released on 2019-04-21
添加了 Python 命令的輸出。運行工具為windows版本Python(3.7)自帶的命令行解釋器。
版權聲明:博客為博主原創,允許轉載,但必須注明原文地址: https://www.cnblogs.com/byronsh/p/10745292.html
本文的數字簽名如下:
MGQCMF13rF3CaChlC2fwTrbc7ajksFng9Cna/V/Eg6rrYVeVeg246Q4E/jLm5crJ5FF5sQIwDu+bzi1fBOof5BSqfx4dsLcBAPTD0R58MbtYXpG9SQiP4AqIhBZjgb6zwK81wX7q
--- 2019-7-20 9:33:43