1、Python 可以在模塊級別暴露接口:
__all__ = ["foo", "bar"]
【注意】:Python 沒有原生的可見性控制,其可見性的維護是靠一套需要大家自覺遵守的”約定“,比如,雙下划線開頭的變量對外部不可見(私有變量)。
① __all__ 是針對模塊公開接口的一種約定,比起雙下划線的方式(私有變量或者私有函數), __all__ 以提供了”白名單“的形式暴露接口。
②一些不以下划線開頭的變量(比如從其他地方 import 到當前模塊的成員)可以同樣被排除出去。
③如果定義了__all__,其他文件中使用 from xxx import * 導入該文件(可以是模塊或者包)時,只會導入 __all__ 列出的成員,可以其他成員都被排除在外。
如,test1.py,test2.py,test3.py三個文件:
test1.py #__all__ = ['func'] def func(): pass
test2.py
import test1 __all__ = ['func2', 'test1'] def func2(): pass def func22(): pass
test3.py from test2 import *
func2()
test1.func()
func22()
程序運行結果: func2() #能正常引用 test1.func() #能正常引用 func22() #不能正常引用
2、控制 from xxx import * 的行為
python3種不提倡用 from xxx import * 這種寫法。如果一個模塊 xxx 沒有定義 __all__ ,執行 from spam import * 時會將 xxx 中所有非下划線開頭的成員(包括該模塊import的其他模塊成員)都會導入當前命名空間,這樣就可能弄臟當前的命名空間。
顯式聲明了 __all__ , import * 就只會導入 __all__ 列出的成員,如果 __all__ 定義有誤,還會明確地拋出異常,方便檢查錯誤。
3、為 lint 等代碼檢查工具提供輔助
編寫一個庫的時候,經常會在 __init__.py 中暴露整個包的 API,而這些 API 的實現可能是在包中其他模塊中定義的。如果我們僅僅這樣寫:
from foo.bar import Spam, Egg
一些代碼檢查工具,如 pyflakes
就會報錯,認為 Spam
和 Egg
是 import
了又沒被使用的變量。當然一個可行的方法是把這個警告壓掉:
from foo.bar import Spam, Egg # noqa
但是更好的方法是顯式定義 __all__
,這樣代碼檢查工具會理解這層意思,就不再報 unused variables
的警告:
from foo.bar import Spam, Egg __all__ = ["Spam", "Egg"]
需要注意的是大部分情況下 __all__
都是一個 list
,而不是 tuple
或者其他序列類型。如果寫了其他類型的 __all__
,如無意外 pyflakes
等 lint 工具會無法識別出。
4、定義 all 需要注意的地方
- 如上所述, __all__ 應該是列表數據類型。
- 不應該動態生成 __all__ ,比如使用列表解析式。 __all__ 的作用就是定義公開接口,如果不以字面量的形式顯式寫出來,就失去意義了。
- 即使有了 __all__ 也不應該在非臨時代碼中使用
from xxx import *
語法,或者用元編程手段模擬 Ruby 的自動import
。Python 不像 Ruby,沒有Module
這種成員,模塊就是命名空間隔離的執行者。如果打破了這一層,而且引入諸多動態因素,生產環境跑的代碼就充滿了不確定性,調試也會非常困難。 - 按照 PEP8 建議的風格,
__all__
應該寫在所有import
語句下面,和函數、常量等模塊成員定義的上面。
如果一個模塊需要暴露的接口改動頻繁,__all__
可以這樣定義:
__all__ = [ "foo", "bar", "egg", ]
這樣修改一個暴露的接口只需要修改一行,方便版本控制的時候看 diff。最后多出的逗號在 Python 中是允許的,符合 PEP8 風格。