有人能夠用Python解釋__all__嗎?

我愈來愈多地使用Python,而且不斷看到在不一樣的__init__.py文件中設置了變量__all__ 。 有人能夠解釋這是什麼嗎? html


#1樓

我只是添加這是爲了精確: python

全部其餘答案均涉及模塊 。 最初的問題在__init__.py文件中明確提到__all__ ,因此這是關於python 包的mysql

一般, __all__僅在使用import語句的from xxx import *變體時才起做用。 這適用於軟件包以及模塊。 sql

模塊的行爲在其餘答案中進行了說明。 包的確切行爲在此處詳細描述。 api

簡而言之,包級別的__all__模塊具備幾乎相同的功能,除了它處理包中的模塊 (與在模塊中指定名稱相反)。 所以__all__指定當咱們from package import *使用時應加載並導入到當前名稱空間的全部模塊。 app

最大的區別是,當您省略軟件包的__init__.py__all__聲明時from package import *的語句將根本不會導入任何內容(文檔中有例外說明,請參見上面的連接)。 函數

另外一方面,若是在模塊中省略__all__ ,則「已加星標的導入」將導入模塊中定義的全部名稱(不如下劃線開頭)。 工具


#2樓

它還更改了pydoc將顯示的內容: 單元測試

module1.py 測試

a = "A"
b = "B"
c = "C"

module2.py

__all__ = ['a', 'b']

a = "A"
b = "B"
c = "C"

$ pydoc module1

Help on module module1:

NAME
    module1

FILE
    module1.py

DATA
    a = 'A'
    b = 'B'
    c = 'C'

$ pydoc module2

Help on module module2:

NAME
    module2

FILE
    module2.py

DATA
    __all__ = ['a', 'b']
    a = 'A'
    b = 'B'

我在全部模塊中都聲明瞭__all__ ,並強調了內部細節,當使用在實時解釋器會話中從未使用過的功能時,這些確實有用。


#3樓

用Python解釋__all__嗎?

我一直看到在不一樣的__init__.py文件中設置了變量__all__

這是作什麼的?

__all__作什麼的?

它從模塊中聲明語義上的「公共」名稱。 若是__all__有一個名稱,則但願用戶使用它,而且他們能夠指望它不會更改。

它也會對程序產生影響:

import *

__all__在模塊中,例如module.py

__all__ = ['foo', 'Bar']

表示從模塊import *時,僅導入__all__中的那些名稱:

from module import *               # imports foo and Bar

文檔工具

文檔和代碼自動完成工具也可能(實際上應該)檢查__all__以肯定哪些名稱能夠從模塊中顯示出來。

__init__.py使目錄成爲Python包

文檔

__init__.py文件是使Python將目錄視爲包含包所必需的; 這樣作是爲了防止具備通用名稱(例如字符串)的目錄無心間隱藏了稍後在模塊搜索路徑中出現的有效模塊。

在最簡單的狀況下, __init__.py能夠只是一個空文件,但它也能夠爲程序包執行初始化代碼或設置__all__變量。

所以__init__.py能夠聲明__all__

管理API:

一個包一般由能夠互相導入但又必須與__init__.py文件捆綁在一塊兒的模塊組成。 該文件使目錄成爲實際的Python包。 例如,假設您具備如下條件:

package/
   |-__init__.py # makes directory a Python package
   |-module_1.py
   |-module_2.py

__init__.py編寫:

from module_1 import *
from module_2 import *

module_1您具備:

__all__ = ['foo',]

module_2您有:

__all__ = ['Bar',]

如今,您已經提供了一個完整的api,供其餘人在導入您的軟件包時使用,以下所示:

import package
package.foo()
package.Bar()

並且,它們將不會具備您在建立使整個package名稱空間混亂的模塊時使用的全部其餘名稱。

__init__.py __all__

通過更多的工做,也許您已經肯定模塊太大,須要拆分。 所以,您須要執行如下操做:

package/
   |-__init__.py
   |-module_1/
   |  |-__init__.py
   |  |-foo_implementation.py
   |-module_2/
      |-__init__.py
      |-Bar_implementation.py

而後在每一個__init__.py聲明一個__all__ ,例如在module_1中:

from foo_implementation import *
__all__ = ['foo']

和module_2的__init__.py

from Bar_implementation import *
__all__ = ['Bar']

並且,您能夠輕鬆地將內容添加到您能夠在子包級別而不是子包的模塊級別管理的API中。 若是要向API添加新名稱,只需更新__init__.py ,例如在module_2中:

from Bar_implementation import *
from Baz_implementation import *
__all__ = ['Bar', 'Baz']

若是您還不許備在頂級API中發佈Baz ,則能夠在頂級__init__.py進行如下操做:

from module_1 import *       # also constrained by __all__'s
from module_2 import *       # in the __init__.py's
__all__ = ['foo', 'Bar']     # further constraining the names advertised

若是您的用戶知道Baz的可用性,則可使用它:

import package
package.Baz()

可是若是他們不知道,其餘工具(例如pydoc )將不會通知他們。

Baz準備好迎接黃金時段時,您能夠稍後更改它:

from module_1 import *
from module_2 import *
__all__ = ['foo', 'Bar', 'Baz']

前綴___all__

默認狀況下,Python將導出全部不以_開頭的名稱。 您固然能夠依靠這種機制。 實際上,Python標準庫中的某些軟件包確實依賴於此,可是要這樣作,它們會爲本身的導入ctypes/__init__.py別名,例如在ctypes/__init__.py

import os as _os, sys as _sys

使用_約定可能更優雅,由於它消除了再次命名名稱的麻煩。 但這增長了導入的冗餘(若是有不少導入的話),很容易忘記一向地執行此操做-最後要作的就是無限期地支持您打算僅做爲實現細節的內容,只是由於您在命名函數時忘記了_前綴。

我在開發生命週期的早期就親自爲模塊編寫了__all__ ,以便其餘可能使用個人代碼的人知道應該使用而不該該使用什麼。

標準庫中的大多數軟件包還使用__all__

避免__all__纔有意義

在如下狀況下,使用_前綴約定代替__all__是有意義的:

  • 您仍處於早期開發模式,沒有用戶,而且不斷調整API。
  • 也許您確實有用戶,可是您擁有涵蓋該API的單元測試,而且您仍在積極地添加到API並進行開發方面的調整。

export裝潢商

使用__all__的缺點是必須編寫兩次導出的函數和類的名稱-而且信息與定義保持分開。 咱們能夠使用裝飾器解決此問題。

我從大衛·比茲利(David Beazley)關於包裝的演講中想到了這樣的出口裝飾商。 在CPython的傳統導入器中,此實現彷佛運行良好。 若是您有一個特殊的導入掛鉤或系統,我不能保證,可是若是您採用它,撤消它就很簡單了-您只須要手動將名稱從新添加到__all__

所以,例如在實用程序庫中,您將定義裝飾器:

import sys

def export(fn):
    mod = sys.modules[fn.__module__]
    if hasattr(mod, '__all__'):
        mod.__all__.append(fn.__name__)
    else:
        mod.__all__ = [fn.__name__]
    return fn

而後,在定義__all__執行此操做:

$ cat > main.py
from lib import export
__all__ = [] # optional - we create a list if __all__ is not there.

@export
def foo(): pass

@export
def bar():
    'bar'

def main():
    print('main')

if __name__ == '__main__':
    main()

不管是做爲主程序運行仍是由其餘函數導入,此方法均可以正常工做。

$ cat > run.py
import main
main.main()

$ python run.py
main

帶有import * API配置也將起做用:

$ cat > run.py
from main import *
foo()
bar()
main() # expected to error here, not exported

$ python run.py
Traceback (most recent call last):
  File "run.py", line 4, in <module>
    main() # expected to error here, not exported
NameError: name 'main' is not defined

#4樓

__all__自定義* from <module> import *

__all__自定義* from <package> import *


模塊是要導入的.py文件。

軟件包是帶有__init__.py文件的目錄。 軟件包一般包含模塊。


模組

""" cheese.py - an example module """

__all__ = ['swiss', 'cheddar']

swiss = 4.99
cheddar = 3.99
gouda = 10.99

__all__讓人們知道模塊的「公共」功能。 [ @AaronHall ]另外,pydoc能夠識別它們。 [ @Longpoke ]

模塊導入*

瞭解如何將swisscheddar引入本地名稱空間,而不是gouda

>>> from cheese import *
>>> swiss, cheddar
(4.99, 3.99)
>>> gouda
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'gouda' is not defined

沒有__all__ ,任何符號(不如下劃線開頭)都將可用。


不帶*進口不受__all__影響


導入模塊

>>> import cheese
>>> cheese.swiss, cheese.cheddar, cheese.gouda
(4.99, 3.99, 10.99)

來自模塊導入名稱

>>> from cheese import swiss, cheddar, gouda
>>> swiss, cheddar, gouda
(4.99, 3.99, 10.99)

導入模塊做爲本地名稱

>>> import cheese as ch
>>> ch.swiss, ch.cheddar, ch.gouda
(4.99, 3.99, 10.99)

包數

__init__.py文件中, __init__.py __all__是包含公用模塊或其餘對象名稱的字符串列表。 這些功能可用於通配符導入。 與模塊同樣, __all__在從軟件包中通配符導入時自定義*[ @MartinStettner ]

這是Python MySQL Connector __init__.py的摘錄:

__all__ = [
    'MySQLConnection', 'Connect', 'custom_error_exception',

    # Some useful constants
    'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption',
    'HAVE_CEXT',

    # Error handling
    'Error', 'Warning',

    ...etc...

    ]

缺省狀況下, 星號不帶__all__的軟件包很是複雜,由於明顯的行爲是很昂貴的:使用文件系統搜索軟件包中的全部模塊。 相反,在閱讀文檔時,僅導入__init__.py中定義的對象:

若是未定義__all__ ,則from sound.effects import *的語句不會將包sound.effects全部子模塊導入當前名稱空間; 它僅確保已導入包sound.effects (可能在__init__.py運行任何初始化代碼),而後導入包中定義的任何名稱。 這包括__init__.py定義的任何名稱(以及明確加載的子模塊)。 它還包括之前的import語句顯式加載的包的全部子模塊。


應避免使用通配符導入,由於它們會使讀者和許多自動化工具感到困惑。

[ PEP 8 ,@ ToolmakerSteve]


#5樓

__all__用於記錄Python模塊的公共API。 儘管它是可選的,但應使用__all__

這是Python語言參考中的相關摘錄:

模塊定義的公共名稱是經過檢查模塊名稱空間中名爲__all__的變量來肯定的; 若是已定義,則必須是由該模塊定義或導入的名稱的字符串序列。 __all__中給出的名稱均被視爲公開名稱,而且必須存在。 若是未定義__all__ ,則公共名稱集將包含在模塊命名空間中找到的全部名稱,這些名稱都不如下劃線字符(_)開頭。 __all__應該包含整個公共API。 目的是避免意外導出不屬於API的項目(例如在模塊中導入和使用的庫模塊)。

PEP 8使用了相似的措辭,儘管它也清楚地代表,當缺乏__all__時,導入的名稱不屬於公共API:

爲了更好地支持自省,模塊應使用__all__屬性在其公共API中顯式聲明名稱。 將__all__設置爲空列表表示該模塊沒有公共API。

[...]

導入的名稱應始終被視爲實現細節。 除非其餘模塊是包含模塊的API中明確記錄的一部分,不然其餘模塊不得依賴對此類導入名稱的間接訪問,例如os.path或從子模塊公開功能的軟件包的__init__模塊。

此外,如其餘答案所指出的, __all__用於啓用軟件包的通配符導入

import語句使用如下約定:若是程序包的__init__.py代碼定義了名爲__all__的列表,則將其視爲遇到from package import *時應導入的模塊名稱的列表。

相關文章
相關標籤/搜索