Hướng dẫn __all__ trong python

Giải thích __all__ trong Python?

Nội dung chính

  • Không gì __all__làm gì?
  • Công cụ tài liệu
  • __init__.py làm cho một thư mục một gói Python
  • Quản lý API:
  • __all__ trong __init__.py
  • Tiền tố _so với __all__:
  • Khi tránh __all__có ý nghĩa
  • Một exportngười trang trí

Tôi tiếp tục nhìn thấy các biến __all__được thiết lập trong các __init__.pytập tin khác nhau .

Cái này làm gì

Không gì __all__làm gì?

Nó tuyên bố các tên "công khai" về mặt ngữ nghĩa từ một mô-đun. Nếu có tên __all__, người dùng sẽ sử dụng nó và họ có thể mong đợi rằng nó sẽ không thay đổi.

Nó cũng sẽ có ảnh hưởng đến chương trình:

import *

__all__trong một mô-đun, ví dụ module.py:

__all__ = ['foo', 'Bar']

có nghĩa là khi bạn import *từ mô-đun, chỉ những tên trong tên __all__được nhập:

from module import *               # imports foo and Bar

Công cụ tài liệu

Các công cụ tự động hoàn thành tài liệu và mã có thể (trên thực tế, cũng nên) kiểm tra __all__để xác định tên nào sẽ hiển thị khi có sẵn từ một mô-đun.

__init__.py làm cho một thư mục một gói Python

Từ các tài liệu :

Các __init__.pytệp được yêu cầu để làm cho Python coi các thư mục là các gói chứa; điều này được thực hiện để ngăn các thư mục có tên chung, chẳng hạn như chuỗi, vô tình ẩn các mô-đun hợp lệ xảy ra sau này trên đường dẫn tìm kiếm mô-đun.

Trong trường hợp đơn giản nhất, __init__.pycó thể chỉ là một tệp trống, nhưng nó cũng có thể thực thi mã khởi tạo cho gói hoặc đặt __all__biến.

Vì vậy, __init__.pycó thể tuyên bố __all__cho một gói .

Quản lý API:

Một gói thường được tạo thành từ các mô-đun có thể nhập lẫn nhau, nhưng nhất thiết phải được gắn với nhau bằng một __init__.pytệp. Tệp đó là thứ làm cho thư mục trở thành một gói Python thực sự. Ví dụ: giả sử bạn có những điều sau đây:

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

trong phần __init__.pybạn viết:

from module_1 import *
from module_2 import *

và trong module_1bạn có:

__all__ = ['foo',]

và trong module_2bạn có:

__all__ = ['Bar',]

Và bây giờ bạn đã trình bày một api hoàn chỉnh mà người khác có thể sử dụng khi họ nhập gói của bạn, như vậy:

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

Và họ sẽ không có tất cả các tên khác mà bạn đã sử dụng khi tạo các mô-đun của mình làm lộn xộn packagekhông gian tên.

__all__ trong __init__.py

Sau khi làm việc nhiều hơn, có thể bạn đã quyết định rằng các mô-đun quá lớn và cần phải tách ra. Vì vậy, bạn làm như sau:

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

Và trong mỗi __init__.pybạn khai báo một __all__, ví dụ như trong module_1:

from foo_implementation import *
__all__ = ['foo']

Và mô-đun_2 __init__.py:

from Bar_implementation import *
__all__ = ['Bar']

Và bạn có thể dễ dàng thêm mọi thứ vào API mà bạn có thể quản lý ở cấp gói phụ thay vì cấp mô-đun của gói phụ. Nếu bạn muốn thêm tên mới vào API, bạn chỉ cần cập nhật __init__.py, ví dụ như trong mô-đun_2:

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

Và nếu bạn chưa sẵn sàng để xuất bản Baztrong API cấp cao nhất, ở cấp cao nhất __init__.pybạn có thể có:

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

và nếu người dùng của bạn biết về tính khả dụng của Baz, họ có thể sử dụng nó:

import package
package.Baz()

nhưng nếu họ không biết về nó, các công cụ khác (như pydoc ) sẽ không thông báo cho họ.

Sau này bạn có thể thay đổi điều đó khi Bazsẵn sàng cho giờ chính:

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

Tiền tố _so với __all__:

Theo mặc định, Python sẽ xuất tất cả các tên không bắt đầu bằng một _. Bạn chắc chắn có thể dựa vào cơ chế này. Một số gói trong thư viện chuẩn của Python, trên thực tế, làm dựa vào điều này, nhưng để làm như vậy, họ bí danh nhập khẩu của họ, ví dụ, trong ctypes/__init__.py:

import os as _os, sys as _sys

Sử dụng _quy ước có thể thanh lịch hơn vì nó loại bỏ sự dư thừa của việc đặt tên một lần nữa. Nhưng nó bổ sung thêm sự dư thừa cho hàng nhập khẩu (nếu bạn có rất nhiều trong số đó) và rất dễ quên làm điều này một cách nhất quán - và điều cuối cùng bạn muốn là phải hỗ trợ vô hạn một cái gì đó mà bạn dự định chỉ là một chi tiết thực hiện, chỉ là bởi vì bạn đã quên tiền tố một _khi đặt tên hàm.

Cá nhân tôi viết __all__sớm trong vòng đời phát triển của mình cho các mô-đun để những người khác có thể sử dụng mã của tôi biết họ nên sử dụng cái gì và không sử dụng.

Hầu hết các gói trong thư viện tiêu chuẩn cũng sử dụng __all__.

Khi tránh __all__có ý nghĩa

Thật hợp lý khi tuân theo _quy ước tiền tố thay cho __all__khi:

  • Bạn vẫn đang ở chế độ phát triển sớm và không có người dùng và liên tục điều chỉnh API của bạn.
  • Có thể bạn có người dùng, nhưng bạn có những điều không hợp lý bao trùm API và bạn vẫn đang tích cực thêm vào API và điều chỉnh trong quá trình phát triển.

Một exportngười trang trí

Nhược điểm của việc sử dụng __all__là bạn phải viết tên của các hàm và lớp được xuất hai lần - và thông tin được tách biệt khỏi các định nghĩa. Chúng ta có thể sử dụng một trang trí để giải quyết vấn đề này.

Tôi có ý tưởng cho một nhà trang trí xuất khẩu như vậy từ bài nói chuyện của David Beazley về bao bì. Việc triển khai này dường như hoạt động tốt trong nhà nhập khẩu truyền thống của CPython. Nếu bạn có một móc hoặc hệ thống nhập khẩu đặc biệt, tôi không đảm bảo, nhưng nếu bạn chấp nhận nó, việc rút lui khá đơn giản - bạn sẽ chỉ cần thêm tên thủ công vào__all__

Vì vậy, trong ví dụ, một thư viện tiện ích, bạn sẽ xác định trình trang trí:

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

và sau đó, nơi bạn sẽ xác định một __all__, bạn làm điều này:

$ 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()

Và điều này hoạt động tốt cho dù chạy như chính hoặc nhập bởi chức năng khác.

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

$ python run.py
main

Và việc cung cấp API import *cũng sẽ hoạt động:

$ 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

135 hữu ích 2 bình luận chia sẻ