41. Gói
41. Gói hàng
Một gói là một mô-đun có thể chứa các mô-đun khác. Trong CPython, gói không phải là một danh mục đối tượng riêng biệt. Nó vẫn là một đối tượng mô-đun, nhưng nó có siêu dữ liệu nhập cho hệ thống nhập biết nơi cần tìm các mô-đun con.
Ở cấp độ Python, thư mục này có thể là một gói:text app/ __init__.py config.py server.py Bạn có thể nhập nó dưới dạng:python import app import app.config from app.server import run Quy tắc quan trọng rất đơn giản:```text
A package is a module with submodule search locations.
## 41.1 Gói là mô-đun
Một đối tượng gói có loại`module`.
```python
import email
print(type(email))
print(email.__name__)
```Đầu ra:```text
<class 'module'>
email
```Một gói có từ điển mô-đun giống như bất kỳ mô-đun nào khác.```python
import email
print(email.__dict__)
```Không gian tên gói lưu trữ tên bình thường:```text
__name__
__doc__
__package__
__loader__
__spec__
__path__
__file__
__cached__
```Nó cũng có thể lưu trữ các hàm, lớp, hằng, mô-đun con đã nhập và tên API công khai được tái xuất.
Do đó, một gói là cả hai:```text
a namespace object
a container for submodule lookup
```## 41.2 Vai trò của`__init__.py`Một gói thông thường thường có`__init__.py`tài liệu.```text
project/
app/
__init__.py
config.py
routes.py
```Khi CPython nhập`app`, nó thực thi:```text
app/__init__.py
```Mã trong`__init__.py`khởi tạo không gian tên gói.
Ví dụ:```python
# app/__init__.py
VERSION = "1.0.0"
def create_app():
return "app"
```Sau đó:```python
import app
print(app.VERSION)
print(app.create_app())
```tập tin`__init__.py`không chỉ là một điểm đánh dấu. Nó là mã mô-đun thực thi.
## 41.3 Nhập gói tối thiểu
Đối với cách bố trí này:```text
demo/
__init__.py
util.py
```và mã này:```python
import demo
```CPython thực hiện đại khái:```text
find package named "demo"
create module object for "demo"
set package metadata
insert "demo" into sys.modules
execute demo/__init__.py
bind name "demo" in caller namespace
```Sau khi nhập:```python
import sys
import demo
print(sys.modules["demo"] is demo)
```Đầu ra:```text
True
```Gói được lưu trữ trong`sys.modules`dưới tên đầy đủ của nó.
## 41.4 Nhập mô-đun con
Dành cho:```python
import demo.util
```CPython nhập gói gốc trước.
Về mặt khái niệm:```text
import demo
then search demo.__path__ for util
then import demo.util
then set demo.util attribute
```Sau khi nhập thành công:```python
import demo.util
print(demo.util)
print(demo.util.__name__)
```Hình dạng đầu ra:```text
<module 'demo.util' from '.../demo/util.py'>
demo.util
```Mô-đun con được lưu trữ riêng:```python
import sys
import demo
import demo.util
print(sys.modules["demo"])
print(sys.modules["demo.util"])
print(demo.util is sys.modules["demo.util"])
```Gói cha và mô-đun con là các đối tượng mô-đun khác nhau.
## 41.5 Tên mô-đun đủ điều kiện
Các gói tạo tên mô-đun phân cấp.```text
app
app.config
app.server
app.server.http
```Mỗi mô-đun được nhập có một tên đủ điều kiện.```python
import app.server.http
print(app.__name__)
print(app.server.__name__)
print(app.server.http.__name__)
```Đầu ra:```text
app
app.server
app.server.http
```Tên đủ điều kiện là khóa được sử dụng trong`sys.modules`.
```python
import sys
print(sys.modules["app"])
print(sys.modules["app.server"])
print(sys.modules["app.server.http"])
```Danh tính dựa trên tên này rất quan trọng. Nhập cùng một tệp dưới hai tên khác nhau có thể tạo ra hai đối tượng mô-đun độc lập.
## 41.6 Địa điểm tìm kiếm gói hàng
Một gói có`__path__`.
```python
import app
print(app.__path__)
```Đối với gói thông thường,`__path__`thường chứa thư mục gói.```text
['/path/to/project/app']
```Khi CPython nhập:```python
import app.config
```nó tìm kiếm`app.__path__`, không phải cấp cao nhất đầy đủ`sys.path`.
Sự khác biệt này là trung tâm:
| Nhập khẩu | Đường dẫn tìm kiếm |
|---|---|
|`import app` | `sys.path` |
| `import app.config` | `app.__path__` |
| `import app.server.http` | `app.server.__path__`|
Một gói kiểm soát nơi các gói con của nó được tìm thấy.
## 41,7`__spec__`và các gói
Mỗi mô-đun nhập khẩu hiện đại đều có`__spec__`.
Đối với các gói, thông số mô-đun bao gồm các vị trí tìm kiếm mô-đun con.```python
import app
print(app.__spec__)
print(app.__spec__.name)
print(app.__spec__.origin)
print(app.__spec__.submodule_search_locations)
```Đối với một gói, giá trị này thường không rỗng:```python
app.__spec__.submodule_search_locations
```Đối với một mô-đun thông thường, nó thường là`None`.
Hệ thống nhập sử dụng điều này để phân biệt các mô-đun với các gói.
## 41.8 Siêu dữ liệu gói
Một gói thông thường thường có các thuộc tính sau:
| Thuộc tính | Ý nghĩa |
|---|---|
|`__name__`| Tên gói đủ điều kiện |
|`__package__`| Bối cảnh gói được sử dụng để nhập tương đối |
|`__path__`| Vị trí tìm kiếm mô-đun con |
|`__spec__`| Đặc điểm nhập khẩu |
|`__loader__`| Trình tải đã khởi tạo gói |
|`__file__`| Đường dẫn đến`__init__.py`, nếu được hỗ trợ bằng tệp |
|`__cached__`| Đường dẫn tới mã byte được lưu trong bộ nhớ đệm, nếu có |
Ví dụ:```python
import json
print(json.__name__)
print(json.__package__)
print(json.__path__)
print(json.__file__)
print(json.__cached__)
```Do đó, một gói có thể được quan sát như một đối tượng bình thường.
##41.9`__package__`các`__package__`thuộc tính kiểm soát độ phân giải nhập tương đối.
Bên trong`app/server.py`:
```python
from .config import Settings
```Dấu chấm ở đầu có nghĩa là:```text
resolve "config" relative to the current package
```Đối với mô-đun`app.server`, bối cảnh gói thường là:```text
app
```Vì thế:```python
from .config import Settings
```quyết tâm:```python
from app.config import Settings
```Đối với một mô-đun gói như`app`, `__package__`thường là`"app"`.
Đối với một mô-đun con như`app.server`, `__package__`thường là`"app"`.
Đối với một mô-đun con lồng nhau như`app.http.server`, `__package__`thường là`"app.http"`.
## 41.10 Lệnh thực thi gói
Với cách bố trí này:```text
app/
__init__.py
config.py
server.py
```và nhập khẩu này:```python
import app.server
```CPython thực thi theo thứ tự sau:```text
1. app/__init__.py
2. app/server.py
```Nếu như`server.py`nhập khẩu`config.py`:
```python
# app/server.py
from . import config
```sau đó việc thực thi sẽ trở thành:```text
1. app/__init__.py
2. app/server.py starts
3. app/config.py executes
4. app/server.py continues
```Các gói cha được tải trước các mô-đun con.
## 41.11 Thuộc tính gói cho mô-đun con
Sau:```python
import app.server
```gói cha thường nhận được một thuộc tính:```python
app.server
```Thuộc tính này trỏ đến đối tượng mô hình con.
Chế độ xem tương đương:```python
import sys
import app.server
assert app.server is sys.modules["app.server"]
```Ràng buộc này quan trọng vì mã người dùng thường điều hướng qua các thuộc tính gói:```python
import app.server
app.server.run()
```Hệ thống nhập duy trì kết nối giữa gói cha và mô-đun con.
## 41.12`import package.module`vs`from package import module`Hai hình thức này giống nhau nhưng không giống nhau về ràng buộc tên.```python
import app.config
```ràng buộc`app`trong không gian tên người gọi.```python
from app import config
```ràng buộc`config`trong không gian tên người gọi.
Cả hai đều tải bình thường`app.config`.
Ví dụ:```python
import app.config
print(app.config)
from app import config
print(config)
```Đối tượng mô-đun được tải thường giống nhau:```python
import app.config
from app import config
print(app.config is config)
```Đầu ra:```text
True
```Sự khác biệt là tên được đặt trong không gian tên của mô-đun nhập.
## 41.13 API gói công khai
Một gói có thể hiển thị API công khai sạch thông qua`__init__.py`.
Bố cục ví dụ:```text
httpkit/
__init__.py
client.py
response.py
errors.py
```Các tập tin nội bộ:```python
# httpkit/client.py
class Client:
...
# httpkit/errors.py
class HTTPKitError(Exception):
...
```Mặt tiền:```python
# httpkit/__init__.py
from .client import Client
from .errors import HTTPKitError
__all__ = ["Client", "HTTPKitError"]
```Người dùng có thể viết:```python
from httpkit import Client, HTTPKitError
```Điều này cho phép tác giả gói thay đổi bố cục tệp nội bộ trong khi vẫn duy trì mục nhập công khai.
## 41,14`__all__`Tên`__all__`xác định tên công khai được sử dụng bởi nhập sao.```python
__all__ = ["Client", "HTTPKitError"]
```Vì:```python
from httpkit import *
```Python nhập tên được liệt kê trong`httpkit.__all__`.
Không có`__all__`, gắn dấu sao importexport tên không bắt đầu bằng`_`.
`__all__`cũng hữu ích như tài liệu. Nó cho người đọc biết tên nào được dùng làm API gói công khai.
## 41.15 Mặt tiền trọn gói và chi phí nhập khẩu
Mặt tiền của gói cải thiện tính tiện dụng nhưng có thể tăng thời gian nhập khẩu.
Điều này rất thuận tiện:```python
# package/__init__.py
from .database import Database
from .server import Server
from .client import Client
from .analytics import Tracker
```Nhưng bây giờ:```python
import package
```tải tất cả các mô-đun đó.
Điều đó có thể tốn kém nếu các mô-đun đó nhập các phần phụ thuộc lớn, khởi tạo thư viện gốc, đọc tệp hoặc thực hiện cấu hình.
Mặt tiền gói nhẹ hơn có thể chỉ hiển thị những cái tên rẻ tiền:```python
# package/__init__.py
__version__ = "1.0.0"
```Sau đó người dùng nhập trực tiếp các thành phần nặng:```python
from package.client import Client
```Thiết kế gói tốt cân bằng giữa sự thuận tiện, thời gian khởi động và sự rõ ràng về sự phụ thuộc.
## 41.16 Thuộc tính gói lười biếng
Một gói có thể hiển thị các thuộc tính một cách lười biếng bằng cách sử dụng cấp độ mô-đun`__getattr__`.
```python
# package/__init__.py
def __getattr__(name):
if name == "Client":
from .client import Client
return Client
raise AttributeError(name)
```Sau đó:```python
import package
Client = package.Client
```nhập khẩu`.client`chỉ khi`Client`được yêu cầu.
Điều này có thể giảm chi phí khởi động trong khi vẫn duy trì API công khai tốt đẹp.
Sự đánh đổi là sự phức tạp. Việc xuất gói lười biếng khiến hành vi nhập ít rõ ràng hơn và lỗi có thể xuất hiện sau đó.
## 41.17 Gói thường
Một gói thông thường có`__init__.py`.
```text
pkg/
__init__.py
mod.py
```Của cải:```text
executes __init__.py when imported
has __file__ pointing to __init__.py
has __path__ pointing to package directory
can define package-level API
can contain submodules and subpackages
```Hầu hết các gói ứng dụng và thư viện đều sử dụng các gói thông thường.
## 41.18 Gói không gian tên
Một gói không gian tên không có một`__init__.py`.
Nó có thể được tập hợp từ nhiều thư mục.
Ví dụ:```text
dir1/
plugins/
alpha.py
dir2/
plugins/
beta.py
```Nếu cả hai`dir1`Và`dir2`đang bật`sys.path`, sau đó`plugins`có thể là một gói không gian tên.```python
import plugins.alpha
import plugins.beta
```Gói`plugins`có thể có một`__path__`chứa cả hai vị trí.
Các gói không gian tên rất hữu ích khi có nhiều bản phân phối đóng góp vào một không gian tên gói.
## 41.19 Gói thông thường và Gói không gian tên
| Tính năng | Gói thông thường | Gói không gian tên |
|---|---|---|
| Có`__init__.py`| Có | Không |
| Thực thi mã khởi tạo gói | Có | Không có trình khởi tạo duy nhất |
| Có thể xác định trực tiếp tên cấp gói | Có | Hạn chế |
| Có thể mở rộng nhiều thư mục | Thường thì không | Có |
| Sử dụng phổ biến | Thư viện và ứng dụng thông thường | Không gian tên plugin, phân phối phân chia |
Một gói thông thường cung cấp sự khởi tạo rõ ràng. Gói không gian tên mang lại sự kết hợp linh hoạt.
## 41.20 Gói con
Gói con là gói bên trong gói khác.```text
app/
__init__.py
api/
__init__.py
users.py
posts.py
```Bạn có thể nhập:```python
import app.api
import app.api.users
```Hệ thống nhập khẩu giải quyết từng cấp độ.```text
app
app.api
app.api.users
```Mỗi cấp độ có đối tượng mô-đun riêng và`sys.modules`lối vào.```python
import sys
import app.api.users
print(sys.modules["app"])
print(sys.modules["app.api"])
print(sys.modules["app.api.users"])
```## 41.21 Nhập khẩu tương đối trong gói
Nhập khẩu tương đối là phổ biến bên trong các gói.```python
from .config import Settings
from .storage import Store
from ..core import errors
```Dấu chấm có nghĩa là mức độ:
| Cú pháp | Ý nghĩa |
|---|---|
|`from . import x`| Nhập anh chị em từ gói hiện tại |
|`from .x import y`| Nhập từ mô-đun con trong gói hiện tại |
|`from .. import x`| Nhập từ gói gốc |
|`from ..x import y`| Nhập từ anh chị em theo gói gốc |
Việc nhập tương đối làm cho các phần phụ thuộc nội bộ trở nên độc lập với tên gói cấp cao nhất.
Họ cũng yêu cầu bối cảnh gói chính xác. Chạy trực tiếp một tập tin gói có thể làm hỏng chúng.
## 41.22 Vấn đề thực thi tập lệnh trực tiếp
Cho:```text
app/
__init__.py
main.py
config.py
```Bên trong`main.py`:
```python
from .config import Settings
```Điều này hoạt động:```bash
python -m app.main
```Điều này có thể thất bại:```bash
python app/main.py
```Khi được thực thi bằng đường dẫn tệp,`main.py`trở thành`__main__`, không`app.main`. Mô-đun có thể mất bối cảnh gói cần thiết cho việc nhập tương đối.
Sử dụng thực thi mô-đun cho mã gói:```bash
python -m app.main
```## 41,23`__main__.py`Một gói có thể định nghĩa`__main__.py`.
```text
tool/
__init__.py
__main__.py
cli.py
```Sau đó:```bash
python -m tool
```thực thi:```text
tool/__main__.py
```Ví dụ:```python
# tool/__main__.py
from .cli import main
main()
```Đây là cách tiêu chuẩn để làm cho một gói có thể thực thi được.
## 41.24 Tác dụng phụ khi khởi tạo gói
Bởi vì`__init__.py`thực thi trong quá trình nhập, việc khởi tạo gói có thể có tác dụng phụ.```python
# package/__init__.py
print("loading package")
connect_to_service()
```Sau đó:```python
import package
```thực hiện ngay công việc đó.
Điều này có thể gây rắc rối cho:```text
startup time
tests
CLI responsiveness
server cold starts
configuration ordering
optional dependencies
import cycles
```Giữ khởi tạo gói nhỏ khi có thể.
Tốt`__init__.py`các tập tin thường chứa:```text
version constants
cheap re-exports
small compatibility shims
public API declarations
```Tránh làm việc nặng nhọc trừ khi việc khởi tạo thời gian nhập là một phần của hợp đồng gói rõ ràng.
## 41.25 Đồ thị phụ thuộc gói
Cấu trúc gói phải phản ánh hướng phụ thuộc.
Một ứng dụng sạch có thể trông giống như:```text
app/
__init__.py
main.py
config.py
domain/
__init__.py
users.py
posts.py
storage/
__init__.py
db.py
web/
__init__.py
routes.py
```Hướng phụ thuộc tốt:```text
main imports web
web imports domain
web imports storage
storage imports domain
domain imports no app-specific infrastructure
```Hướng phụ thuộc kém:```text
domain imports web
storage imports main
config imports route handlers
__init__.py imports everything
```Biểu đồ phụ thuộc gói không hợp lệ thường tạo ra hoạt động nhập vòng tròn.
## 41.26 Nhập khẩu theo gói theo vòng tròn
Nhập vòng tròn là phổ biến trong các gói vì các mô-đun thường nhập anh chị em.
Ví dụ:```python
# app/users.py
from .posts import Post
class User:
...
# app/posts.py
from .users import User
class Post:
...
```Điều này có thể thất bại vì`app.users`Và`app.posts`cần nhau trong quá trình thực thi cấp cao nhất.
Các bản sửa lỗi có thể:
Di chuyển các định nghĩa được chia sẻ:```text
app/
models.py
users.py
posts.py
```Sử dụng nhập khẩu địa phương:```python
def create_post():
from .posts import Post
return Post()
```Sử dụng nhập khẩu chỉ kiểm tra loại:```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .posts import Post
```Việc sửa chữa cấu trúc thường là tốt nhất. Nhập khẩu tuần hoàn thường chỉ ra rằng ranh giới mô-đun cần điều chỉnh.
## 41.27 Tái xuất cấp gói và nhập khẩu tuần hoàn
Tái xuất quá nhiều vào`__init__.py`có thể tạo ra chu kỳ nhập khẩu.
Ví dụ:```python
# app/__init__.py
from .server import Server
from .config import Config
```Sau đó bên trong`server.py`:
```python
from app import Config
```Lực lượng này`app.__init__`để hoàn thành trong khi nó vẫn đang nhập`server`.
Nhập khẩu nội bộ an toàn hơn là:```python
from .config import Config
```Bên trong một gói, ưu tiên nhập từ mô-đun xác định hơn là từ mặt tiền của gói.
Mặt tiền gói chủ yếu dành cho người dùng bên ngoài.
## 41.28 Mô-đun riêng
Python sử dụng quy ước đặt tên cho các mô-đun riêng tư.```text
package/
__init__.py
public.py
_internal.py
_compat.py
```Dấu gạch dưới ở đầu có nghĩa là mô-đun này là mô-đun nội bộ theo quy ước.```python
from package._internal import helper
```Điều này được cho phép, nhưng tác giả gói có thể thay đổi`_internal`mà không bảo toàn tính tương thích.
API công khai phải được ghi lại và tái xuất một cách có chủ ý.
## 41,29`src`Bố cục
Nhiều dự án Python sử dụng`src`cách trình bày.```text
project/
pyproject.toml
src/
package/
__init__.py
core.py
tests/
test_core.py
```Bố cục này giúp ngăn chặn việc nhập ngẫu nhiên từ thư mục gốc của kho lưu trữ.
Không có`src`, các cuộc kiểm tra có thể vô tình nhập các tệp cục bộ ngay cả khi gói đã cài đặt bị hỏng.
Với`src`, gói phải được cài đặt hoặc đường dẫn phải được cấu hình chính xác, phù hợp hơn với hành vi của người dùng.
## 41.30 Dữ liệu gói
Các gói có thể chứa các tập tin dữ liệu.```text
package/
__init__.py
templates/
page.html
data/
defaults.json
```Đừng cho rằng dữ liệu gói nằm trong thư mục hệ thống tệp thông thường. Các gói có thể được nhập từ tệp zip hoặc các trình tải khác.
Thích hơn`importlib.resources`:
```python
from importlib.resources import files
data = files("package.data").joinpath("defaults.json").read_text()
```Điều này yêu cầu hệ thống nhập tài nguyên thay vì xây dựng đường dẫn thủ công từ`__file__`.
## 41,31`__file__`Hạn chế
Nhiều gói có`__file__`.
```python
import package
print(package.__file__)
```Nhưng mã mạnh mẽ không nên cho rằng tất cả các mô-đun và gói đều có đường dẫn tệp bình thường.
Một số mô-đun có thể là:```text
built in
frozen
loaded from zip files
loaded by custom importers
namespace packages
```Đối với tài nguyên gói, hãy sử dụng API hệ thống nhập thay vì số học đường dẫn trực tiếp khi có thể.
## 41.32 Giá trị phiên bản gói
Các gói thường xác định`__version__`.
```python
# package/__init__.py
__version__ = "1.2.3"
```Điều này là đơn giản và phổ biến.
Một gói cũng có thể sử dụng siêu dữ liệu gói đã cài đặt:```python
from importlib.metadata import version
__version__ = version("package-name")
```Cách tiếp cận thứ hai tránh trùng lặp chuỗi phiên bản nhưng có thể thất bại nếu gói không được cài đặt làm siêu dữ liệu.
Đối với các thư viện, hãy đảm bảo việc xử lý phiên bản luôn đơn giản và có thể dự đoán được.
## 41.33 Độ ổn định của API công khai
Bố cục gói và API công khai là những thứ khác nhau.
Bố trí bên trong:```text
library/
_client.py
_transport.py
_errors.py
```API công khai:```python
from library import Client, LibraryError
```Gói có thể bảo toàn API công khai trong khi thay đổi nội bộ.```python
# library/__init__.py
from ._client import Client
from ._errors import LibraryError
__all__ = ["Client", "LibraryError"]
```Sự tách biệt này mang lại cho người bảo trì quyền tự do tái cấu trúc.
## 41.34 Thời gian nhập gói
Thời gian nhập gói có thể được đo.```bash
python -X importtime -c "import package"
```Nhập hàng trọn gói chậm thường xuất phát từ:```text
large transitive imports
heavy package __init__.py files
runtime configuration loading
native library initialization
network or file-system work
plugin auto-discovery
large type-hint imports at runtime
```Sự cải tiến thường bắt đầu bằng việc thực hiện`__init__.py`nhỏ hơn.
## 41.35 Mẫu khởi tạo gói
Một thực tế`__init__.py`thường trông giống như:```python
"""
Public API for examplekit.
"""
from .client import Client
from .errors import ExampleKitError
__all__ = [
"Client",
"ExampleKitError",
]
__version__ = "0.1.0"
```Điều này có thể chấp nhận được khi`client`Và`errors`nhập khẩu rẻ.
Đối với các mô-đun nặng hơn:```python
__all__ = ["Client", "ExampleKitError", "__version__"]
__version__ = "0.1.0"
def __getattr__(name):
if name == "Client":
from .client import Client
return Client
if name == "ExampleKitError":
from .errors import ExampleKitError
return ExampleKitError
raise AttributeError(name)
```Chỉ sử dụng hình thức lười biếng khi chi phí khởi động cao hơn mức độ phức tạp.
## 41.36 Gói và Nội bộ CPython
Ở cấp độ CPython, nhập gói vẫn là nhập mô-đun.
Sự khác biệt xuất hiện trong siêu dữ liệu nhập:```text
regular module:
__spec__.submodule_search_locations = None
package:
__spec__.submodule_search_locations = [...]
__path__ = [...]
```Khi nhập mô-đun con, hệ thống nhập sẽ sử dụng đường dẫn gói cha.
Đơn giản hóa:```python
def import_child(parent, child_name):
fullname = parent.__name__ + "." + child_name
path = parent.__path__
spec = find_spec(fullname, path)
return load(spec)
```Hệ thống nhập thực sự xử lý các giao thức khóa, lỗi, gói vùng tên, bộ nhớ đệm và trình tải.
## 41.37 Đối tượng gói và Tra cứu thuộc tính
Đối tượng gói sử dụng tra cứu thuộc tính mô-đun thông thường.```python
import package
package.name
```Điều này sẽ tìm trong từ điển gói.
Nếu một gói xác định cấp độ mô-đun`__getattr__`, các thuộc tính bị thiếu có thể được tính toán linh hoạt.```python
def __getattr__(name):
...
```Nhưng các mô-đun con được nhập vào thường được lưu trữ dưới dạng thuộc tính trên gói gốc.```python
import package.submodule
print(package.submodule)
```Đây là lý do tại sao không gian tên gói có thể phát triển khi quá trình nhập diễn ra.
## 41.38 Nhập gói không thành công
Nếu quá trình nhập gói không thành công trong quá trình`__init__.py`, hệ thống nhập sẽ loại bỏ mô-đun bị lỗi khỏi`sys.modules`trong nhiều trường hợp.
Ví dụ:```python
# broken/__init__.py
raise RuntimeError("failed")
```Sau đó:```python
import broken
```đưa ra một ngoại lệ.
Hệ thống nhập phải tránh để mô-đun bị hỏng được lưu vào bộ nhớ đệm như thể nó đã được khởi tạo thành công.
Lỗi mô-đun con có thể tinh vi hơn. Gói cha có thể nhập thành công trong khi mô-đun con không thành công.
## 41.39 Nhận dạng gói trùng lặp
Nhận dạng gói trùng lặp xảy ra khi có thể nhập cùng một gói dưới các tên khác nhau.
Vấn đề về đường dẫn ví dụ:```text
project/
app/
__init__.py
state.py
```Một đường dẫn nhập:```python
import app.state
```Một con đường tình cờ khác:```python
import state
```Bây giờ cùng một tệp có thể được tải hai lần:```text
sys.modules["app.state"]
sys.modules["state"]
```Hậu quả:```text
two module global dictionaries
two singleton instances
two registry objects
two class identities
two caches
```Đây là nguồn phổ biến của các lỗi lạ.
Tránh nó bằng cách sử dụng nhập gói tuyệt đối nhất quán và tránh không an toàn`sys.path`chỉnh sửa.
## 41.40 Gói và Nhận dạng loại
Lớp là các đối tượng được lưu trữ trong các mô-đun.
Nếu cùng một mô-đun được nhập hai lần dưới các tên khác nhau thì các lớp của nó sẽ được tạo hai lần.```python
# app/models.py
class User:
pass
```Nếu được tải như cả hai:```python
import app.models
import models
```sau đó:```python
app.models.User is models.User
```Có lẽ:```text
False
```Một đối tượng được tạo từ một lớp có thể bị lỗi`isinstance`kiểm tra đối với người khác.
Đây là lý do tại sao việc nhận dạng mô-đun lại quan trọng đối với các gói.
## 41.41 Gói và Điểm vào
Các gói đã cài đặt có thể hiển thị tập lệnh bảng điều khiển thông qua siêu dữ liệu đóng gói.
Một điểm vào dòng lệnh có thể gọi:```text
package.module:function
```Về mặt khái niệm, việc chạy lệnh sẽ nhập mô-đun và gọi hàm.
Mục tiêu ví dụ:```python
# tool/cli.py
def main():
...
```Điểm vào:```text
tool = tool.cli:main
```Điều này có nghĩa là chi phí khởi động CLI bao gồm việc nhập`tool.cli`và sự phụ thuộc của nó.
Giữ các mô-đun nhập CLI ở mức nhỏ khi khởi động.
## 41.42 Gói plugin
Các gói thường hỗ trợ plugin.
Kiến trúc plugin có thể sử dụng:```text
namespace packages
entry points
importlib
explicit plugin lists
dynamic discovery
```Trình tải plugin rõ ràng đơn giản:```python
import importlib
def load_plugin(name):
return importlib.import_module(f"app_plugins.{name}")
```Hệ thống plugin dựa trên gói nên tránh nhập mọi plugin một cách háo hức trừ khi cần thiết.
Việc nhập plugin thường có tác dụng phụ, chẳng hạn như đăng ký.```python
# plugin_alpha.py
from registry import register
register("alpha", handler)
```Điều này hữu ích nhưng lệnh nhập sẽ trở thành một phần của hoạt động của chương trình.
## 41.43 Gói và tên phân phối
Tên gói nhập và tên gói phân phối có thể khác nhau.
Ví dụ:```text
distribution name: beautifulsoup4
import name: bs4
```Hệ thống nhập biết tên nhập. Công cụ đóng gói biết tên phân phối.
Sự khác biệt này quan trọng khi đọc danh sách phụ thuộc hoặc siêu dữ liệu gói.```python
import bs4
```không nói bản phân phối đã cài đặt đã được đặt tên`bs4`.
## 41.44 Gói và`pyproject.toml`Các gói Python hiện đại thường được sử dụng`pyproject.toml`.
Một dự án có thể tuyên bố:```toml
[project]
name = "examplekit"
version = "0.1.0"
```Tên phân phối là`examplekit`.
Gói nhập khẩu có thể là:```text
src/examplekit/
__init__.py
```Đối với hệ thống nhập của CPython, chỉ bố cục gói có thể nhập và`sys.path`vấn đề trong thời gian chạy. Xây dựng các vấn đề về siêu dữ liệu trước khi chạy, trong quá trình cài đặt và đóng gói.
## 41,45 Số lượt cài đặt có thể chỉnh sửa
Trong quá trình phát triển, các gói thường được cài đặt ở chế độ có thể chỉnh sửa.```bash
python -m pip install -e .
```Điều này làm cho gói có thể được nhập từ cây đang làm việc.
Từ quan điểm của CPython, kết quả vẫn là nhập dựa trên đường dẫn. Môi trường đã được định cấu hình để vị trí nguồn gói xuất hiện ở độ phân giải nhập.
Các bản cài đặt có thể chỉnh sửa giúp kiểm tra và các công cụ nhập gói như người dùng thực hiện trong khi vẫn sử dụng các tệp nguồn trực tiếp.
## 41.46 Danh sách kiểm tra gỡ lỗi gói
Khi quá trình nhập gói hoạt động kỳ lạ, hãy kiểm tra:```python
import sys
import package
print(package)
print(package.__name__)
print(getattr(package, "__file__", None))
print(getattr(package, "__path__", None))
print(package.__spec__)
print(sys.modules.get("package"))
```Đối với một mô-đun con:```python
import package.submodule
print(package.submodule)
print(package.submodule.__name__)
print(package.submodule.__file__)
print(sys.modules.get("package.submodule"))
```Đối với các vấn đề về đường dẫn:```python
import sys
for p in sys.path:
print(p)
```Để giải quyết mà không cần nhập:```python
import importlib.util
print(importlib.util.find_spec("package"))
print(importlib.util.find_spec("package.submodule"))
```## 41.47 Mô hình nhập gói tối thiểu
Một mô hình đơn giản hóa để nhập mô-đun con:```python
def import_package_child(parent_name, child_name):
parent = import_module(parent_name)
fullname = parent_name + "." + child_name
if fullname in sys.modules:
return sys.modules[fullname]
spec = find_spec(fullname, parent.__path__)
if spec is None:
raise ModuleNotFoundError(fullname)
module = module_from_spec(spec)
sys.modules[fullname] = module
try:
spec.loader.exec_module(module)
except Exception:
del sys.modules[fullname]
raise
setattr(parent, child_name, module)
return module
```Mô hình này nắm bắt các phần cụ thể của gói:```text
load parent first
search parent.__path__
cache child by fully qualified name
bind child as parent attribute
```##41.48 Quy tắc thiết kế bao bì tốt
Giữ khởi tạo gói nhỏ.
Sử dụng`__init__.py`để hiển thị API công khai ổn định, không chạy ứng dụng.
Ưu tiên nhập nội bộ trực tiếp từ việc xác định các mô-đun.
Tránh nhập mô-đun nội bộ từ mặt tiền gói.
Tránh thực thi tập lệnh trực tiếp cho các mô-đun gói. Sử dụng`python -m package.module`.
Sử dụng`importlib.resources`cho dữ liệu gói.
Sử dụng`__all__`để ghi lại tên công cộng.
Chỉ sử dụng các gói không gian tên khi cần thành phần gói chia nhỏ.
Tránh sửa đổi`sys.path`mã gói bên trong.
Giữ hướng phụ thuộc rõ ràng.
## 41.49 Lỗi gói phổ biến
| Triệu chứng | Nguyên nhân có thể |
|---|---|
| Nhập tương đối không thành công | Mô-đun chạy dưới dạng tập lệnh thay vì bằng`-m`|
| Gói khởi tạo một phần | Nhập khẩu tuần hoàn |
| Thiếu thuộc tính gói | Mô-đun con chưa được nhập hoặc mặt tiền không xuất nó |
| Hai trường hợp đơn lẻ | Cùng một mô-đun được nhập dưới hai tên |
| Chậm`import package`| Nặng`__init__.py`hoặc nhập khẩu chuyển tiếp |
| Hoạt động trong repo nhưng không thành công sau khi cài đặt | Bố cục gói không hợp lệ hoặc thiếu dữ liệu gói |
| Mô-đun tiêu chuẩn bị che khuất | Xung đột tên gói hoặc tệp cục bộ |
## 41,50 Điểm chính
Một gói là một mô-đun có các vị trí tìm kiếm mô-đun con.
Một gói thông thường thực thi`__init__.py`trong quá trình nhập khẩu.
Các mô-đun con được lưu trữ trong`sys.modules`dưới những cái tên đầy đủ.
Một gói`__path__`kiểm soát nơi các mô-đun con được tìm kiếm.
Nhập khẩu tương đối phụ thuộc vào bối cảnh gói.
Các gói không gian tên cho phép một không gian tên gói trải rộng trên nhiều vị trí.
Mặt tiền gói cải thiện tính công thái học của API nhưng có thể làm tăng chi phí nhập khẩu và tạo ra chu kỳ.
Hầu hết các lỗi gói đều đến từ quá trình nhập vòng tròn, lỗi đường dẫn, nhận dạng mô-đun trùng lặp, khởi tạo nặng hoặc chạy trực tiếp các tệp gói.