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í
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í
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__.py
tậ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__.py
tệ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__.py
có 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__.py
có 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__.py
tệ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__.py
bạn
viết:
from module_1 import *
from module_2 import *
và trong module_1
bạn có:
__all__ = ['foo',]
và trong module_2
bạ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
package
khô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__.py
bạ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 Baz
trong API cấp cao nhất, ở cấp cao nhất
__init__.py
bạ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
Baz
sẵ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
main[] # expected to error here, not exported
NameError: name 'main' is not defined
135 hữu ích 2 bình luận chia sẻ