40. Mô-đun và nhập khẩu
40. Mô-đun và nhập khẩu
Mô-đun là đơn vị tải mã cơ bản, cách ly không gian tên và tái sử dụng của Python. Trong CPython, mô-đun vừa là đối tượng cấp độ ngôn ngữ vừa là bản ghi thời gian chạy trong hệ thống nhập.
Ở cấp độ Python, mô-đun là thứ bạn nhận được sau khi thực thi:python import math import os import json Mỗi tên đã nhập được liên kết với một đối tượng mô-đun, đối tượng gói, hàm, lớp hoặc đối tượng được xuất khác. Ở cấp độ CPython, nhập là một quá trình phối hợp bao gồm các hướng dẫn mã byte, móc nhập, thông số mô-đun, trình tải, công cụ tìm,sys.modules, đường dẫn gói, tra cứu hệ thống tệp, bộ đệm mã byte, khóa nhập và thực thi mô-đun.
Hệ thống nhập không phải là một cơ chế bao gồm tệp đơn giản. Nó là một giao thức thời gian chạy.
40.1 Mô-đun là gì
Một mô-đun là một đối tượng thuộc loạimodule.
import sys
print(type(sys))
print(sys.__name__)
```Đầu ra:```text
<class 'module'>
sys
```Một đối tượng mô-đun sở hữu một từ điển. Từ điển đó là không gian tên chung của mô-đun.```python
import math
print(math.__dict__["pi"])
print(math.__dict__["sqrt"])
```Khi CPython thực thi một tệp mô-đun, các bài tập cấp cao nhất sẽ ghi vào từ điển này.
Đối với một tập tin có tên`config.py`:
```python
debug = True
port = 8080
def connect():
return port
```CPython tạo một đối tượng mô-đun, chuẩn bị không gian tên của nó, thực thi đối tượng mã được biên dịch bên trong không gian tên đó và để lại các ràng buộc kết quả trong`config.__dict__`.
Về mặt khái niệm:```text
module object
__dict__
"__name__" -> "config"
"__file__" -> ".../config.py"
"__spec__" -> ModuleSpec(...)
"debug" -> True
"port" -> 8080
"connect" -> function object
```Do đó, một mô-đun là một đối tượng không gian tên có thể thay đổi.
## 40.2 Đối tượng mô-đun trong CPython
Trong CPython, các đối tượng mô-đun được triển khai bởi`PyModuleObject`kiểu.
Một mô hình tinh thần đơn giản hóa là:```c
typedef struct {
PyObject_HEAD
PyObject *md_dict;
PyObject *md_name;
PyObject *md_doc;
PyObject *md_state;
PyObject *md_weaklist;
PyModuleDef *md_def;
} PyModuleObject;
```Các trường chính xác có thể thay đổi, nhưng điểm quan trọng là ổn định: một mô-đun có từ điển liên quan và định nghĩa/trạng thái mô-đun cấp C tùy chọn.
Từ điển lưu trữ tên Python thông thường. Khi mã Python đánh giá tên chung bên trong một mô-đun, CPython thường tìm trong từ điển mô-đun đó trước tiên.
Ví dụ:```python
x = 10
def f():
return x
```chức năng`f`không sao chép`x`. Nó lưu trữ một tham chiếu đến từ điển toàn cục của mô-đun thông qua đối tượng hàm của nó. Khi`f`thi hành`return x`, CPython giải quyết`x`sử dụng tra cứu toàn cầu đối với từ điển đó.
## 40.3 Nhập là thực thi
Nhập mô-đun Python sẽ thực thi mã cấp cao nhất của nó.
Vì`example.py`:
```python
print("loading example")
value = 42
def get_value():
return value
```Lần nhập đầu tiên thực thi tệp:```python
import example
```Đầu ra:```text
loading example
```Lần nhập thứ hai thường không thực thi lại tệp:```python
import example
```Không có đầu ra nào xuất hiện vì CPython tìm thấy mô-đun hiện có trong`sys.modules`.
Hành vi này là cơ bản. Quá trình nhập có tác dụng phụ vì mã cấp cao nhất của mô-đun chạy.
Mã cấp cao nhất của mô-đun tốt thường chứa các định nghĩa và khởi tạo rẻ tiền:```python
CONSTANT = 100
def parse(text):
...
```Mã cấp cao nhất của mô-đun rủi ro thực hiện công việc tốn kém hoặc có thể nhìn thấy được bên ngoài:```python
connect_to_database()
delete_old_files()
start_threads()
make_network_request()
```Mã như vậy chạy trong quá trình nhập, đôi khi trước khi ứng dụng được khởi chạy hoàn toàn.
## 40.4 Tuyên bố nhập khẩu
Tuyên bố:```python
import package.module
```không trực tiếp có nghĩa là “mở tập tin này.”
Nó có nghĩa là:```text
resolve a module name
find a module specification
create or reuse a module object
initialize import-related attributes
execute the module if needed
bind a name in the caller's namespace
```Tuyên bố:```python
import os.path
```thường ràng buộc`os`, không`os.path`, trong không gian tên cục bộ:```python
import os.path
print(os)
print(os.path)
```Tuyên bố:```python
from os import path
```ràng buộc`path`trực tiếp:```python
from os import path
print(path)
```Tuyên bố:```python
from math import sqrt
```nhập mô-đun nếu cần, sau đó truy xuất`sqrt`từ mô-đun đó và liên kết nó trong không gian tên của người gọi.
## 40,5 Mã byte để nhập
CPython biên dịch các câu lệnh nhập thành mã byte.
Ví dụ:```python
import math
```Mã được biên dịch sử dụng các hướng dẫn mã byte liên quan đến nhập. Trình tự hướng dẫn chính xác thay đổi tùy theo phiên bản Python, nhưng về mặt khái niệm, nó thực hiện điều này:```text
load import machinery
import module named "math"
bind result to name "math"
```Vì:```python
from math import sqrt
```về mặt khái niệm mã byte thực hiện điều này:```text
import module named "math"
load attribute "sqrt"
bind local/global name "sqrt"
```Bạn có thể kiểm tra điều này với`dis`:
```python
import dis
def f():
import math
return math.sqrt(9)
dis.dis(f)
```Câu lệnh nhập là một phần của việc thực thi mã byte thông thường. Không có bước tiền xử lý riêng biệt.
## 40,6`__import__`Ở cấp độ ngôn ngữ, các câu lệnh nhập cuối cùng sẽ định tuyến thông qua bộ máy nhập được hiển thị thông qua`builtins.__import__`.
```python
import builtins
print(builtins.__import__)
```Bạn có thể gọi nó trực tiếp:```python
math_module = __import__("math")
print(math_module.sqrt(9))
```Nhưng hầu hết mã không nên gọi`__import__`trực tiếp. Sử dụng`importlib.import_module`đối với nhập khẩu động:```python
import importlib
mod = importlib.import_module("math")
print(mod.sqrt(9))
```chức năng`__import__`tồn tại vì quá trình nhập là động. Mã Python có thể nhập mô-đun theo tên chuỗi khi chạy.
##40.7`sys.modules`
`sys.modules`là bộ đệm mô-đun trung tâm.
Nó là một từ điển ánh xạ tên mô-đun đủ điều kiện tới các đối tượng mô-đun.```python
import sys
import math
print(sys.modules["math"] is math)
```Đầu ra:```text
True
```Trước khi tải mô-đun, hệ thống nhập sẽ kiểm tra`sys.modules`.
Về mặt khái niệm:```python
if fullname in sys.modules:
return sys.modules[fullname]
else:
module = load_module(fullname)
sys.modules[fullname] = module
return module
```Quá trình thực sự cẩn thận hơn vì nó phải xử lý các gói, nhập vòng tròn, nhập không thành công, khóa và giao thức trình tải.
Thuộc tính chính vẫn còn: nội dung nhập được lưu vào bộ đệm theo tên mô-đun.
## 40.8 Tại sao các mô-đun được chèn trước khi thực thi
CPython thường chèn một mô-đun vào`sys.modules`trước khi thực thi mã của nó.
Điều này là cần thiết cho việc nhập khẩu tuần hoàn.
Giả định`a.py`chứa:```python
import b
x = 1
```Và`b.py`chứa:```python
import a
y = 2
```Khi`a`nhập khẩu`b`, Và`b`nhập khẩu`a`, hệ thống nhập phải tránh đệ quy vô hạn. Nó thực hiện điều này bằng cách đặt đối tượng mô-đun được khởi tạo một phần vào`sys.modules`.
Sự cân bằng là việc nhập khẩu tuần hoàn có thể quan sát các mô-đun chưa hoàn thiện.
Ví dụ:```python
# a.py
import b
x = 1
# b.py
import a
print(a.x)
```Điều này có thể thất bại vì`a.x`vẫn chưa được phân công khi`b`đọc nó.
Nhập khẩu tuần hoàn không bị cấm, nhưng cần phải thận trọng. Cách khắc phục thông thường là di chuyển nội dung nhập vào bên trong các hàm, di chuyển các định nghĩa được chia sẻ sang mô-đun thứ ba hoặc tránh sự phụ thuộc chéo cấp cao nhất.
## 40.9 Trình tự khởi tạo mô-đun
Trình tự nhập đơn giản hóa cho mô-đun nguồn Python trông như thế này:```text
1. Receive module name, such as "pkg.mod".
2. Check sys.modules.
3. Search sys.meta_path for a finder.
4. Finder returns a ModuleSpec.
5. Import machinery creates a module object.
6. Module is inserted into sys.modules.
7. Loader executes module code.
8. Import machinery returns the module object.
9. Import statement binds names in caller namespace.
```Đối với tệp nguồn, việc thực thi có nghĩa là:```text
read source
decode source
compile source to code object
execute code object in module namespace
```Đối với mô-đun mở rộng, việc thực thi có nghĩa là gọi mã khởi tạo gốc.
Đối với mô-đun tích hợp, việc thực thi sử dụng logic khởi tạo tích hợp được biên dịch vào CPython.
## 40.10`ModuleSpec`Sử dụng nhập Python hiện đại`ModuleSpec`các đối tượng để mô tả cách tải một mô-đun.
Thông số mô-đun chứa thông tin như:```text
module name
loader
origin
package search locations
cached bytecode path
whether the module is a package
```Bạn có thể kiểm tra thông số kỹ thuật của mô-đun:```python
import json
print(json.__spec__)
print(json.__spec__.name)
print(json.__spec__.origin)
print(json.__spec__.loader)
print(json.__spec__.submodule_search_locations)
```Đối với một mô-đun bình thường,`submodule_search_locations`thường là`None`.
Đối với một gói, nó chứa các đường dẫn có thể tìm thấy các mô-đun con.
## 40.11 Trình tìm kiếm và Trình tải
Hệ thống nhập tách biệt việc tìm kiếm và tải.
Người tìm kiếm trả lời:```text
Can this module name be found?
If yes, what spec describes it?
```Một người tải trả lời:```text
How should this module be created and executed?
```Sự tách biệt này cho phép Python nhập từ nhiều nơi:```text
source files
bytecode files
built-in modules
extension modules
zip archives
namespace packages
custom import hooks
memory-backed module stores
remote systems, if a custom importer implements it
```Hệ thống nhập tiêu chuẩn có thể mở rộng vì nó dựa trên giao thức.
## 40.12`sys.meta_path`
`sys.meta_path`là điểm kết nối chính đầu tiên trong hệ thống nhập khẩu.
Nó là một danh sách các đối tượng tìm kiếm. Mỗi công cụ tìm có thể quyết định xem nó có biết cách xử lý tên mô-đun hay không.```python
import sys
for finder in sys.meta_path:
print(finder)
```Một tìm kiếm nhập đơn giản thực hiện điều này:```python
for finder in sys.meta_path:
spec = finder.find_spec(fullname, path, target)
if spec is not None:
return spec
```Xử lý các mục điển hình:```text
built-in modules
frozen modules
path-based modules
```Công cụ tìm kiếm dựa trên đường dẫn chịu trách nhiệm tìm kiếm các thư mục và các mục nhập đường dẫn khác.
## 40,13`sys.path`
`sys.path`là danh sách các vị trí tìm kiếm nhập cho các mô-đun cấp cao nhất.```python
import sys
for entry in sys.path:
print(entry)
```Khi bạn viết:```python
import mymodule
```Và`mymodule`không được tích hợp sẵn hoặc bị đóng băng, hệ thống nhập dựa trên đường dẫn sẽ tìm kiếm các mục trong`sys.path`.
Các mục thường là:```text
directory of the running script
current working directory in interactive mode
standard library directories
site-packages directories
paths from PYTHONPATH
virtual environment paths
zip archives
```Đây là lý do tại sao việc thay đổi`sys.path`thay đổi hành vi nhập khẩu.```python
import sys
sys.path.insert(0, "/custom/modules")
import mymodule
```Điều này có thể hữu ích trong các công cụ được kiểm soát nhưng cũng có thể tạo ra hành vi nhập dễ hỏng.
## Gói 40.14
Một gói là một mô-đun có thể chứa các mô-đun con.
Về mặt lịch sử, một thư mục đã trở thành một gói bằng cách chứa một`__init__.py`tài liệu:```text
pkg/
__init__.py
parser.py
lexer.py
```Sau đó:```python
import pkg.parser
```tải`pkg`đầu tiên, sau đó`pkg.parser`.
tập tin`pkg/__init__.py`thực thi khi gói được nhập.
Ví dụ:```python
# pkg/__init__.py
print("loading package")
version = "1.0"
import pkg
print(pkg.version)
```Một gói vẫn là một đối tượng mô-đun. Sự khác biệt là nó có vị trí tìm kiếm gói.
## 40.15 Thuộc tính gói
Các gói thường xác định các thuộc tính liên quan đến nhập:```text
__name__
__package__
__path__
__spec__
__file__
__cached__
```Thuộc tính quan trọng dành riêng cho gói là`__path__`.
```python
import package
print(package.__path__)
__path__cho hệ thống nhập biết nơi tìm kiếm các mô-đun con bên trong gói đó.
Vì:python import package.submodule tìm kiếm hệ thống nhập khẩupackage.__path__, không phải cấp cao nhấtsys.path.
40.16 Gói không gian tên
Python hỗ trợ các gói không gian tên. Đây là những gói không có đơn lẻ__init__.pytài liệu.
Một gói không gian tên có thể được trải rộng trên nhiều thư mục.
Ví dụ:```text dir1/ plugins/ alpha.py
dir2/
plugins/
beta.py
```Nếu cả haidir1Vàdir2đang bậtsys.path, Python có thể xử lýpluginsdưới dạng gói không gian tên.
Sau đó cả hai có thể hoạt động:```python import plugins.alpha import plugins.beta
Chúng cũng làm cho việc nhập dữ liệu trở nên phức tạp hơn vì một gói có thể có nhiều vị trí tìm kiếm.
## 40.17 Nhập khẩu tuyệt đối
Quá trình nhập tuyệt đối bắt đầu từ không gian tên nhập cấp cao nhất.```python
import package.module
from package import module
```Bên trong một gói, nó vẫn tìm kiếm gói cấp cao nhất có tên`package`.
Nhập khẩu tuyệt đối được ưu tiên để làm rõ khi đề cập đến các mô-đun bên ngoài hoặc cấp cao nhất.
Ví dụ:```python
from project.config import Settings
```Điều này cho người đọc biết tên được nhập đến từ đâu.
## 40,18 Nhập khẩu tương đối
Việc nhập tương đối được giải quyết dựa trên gói hiện tại.```python
from . import parser
from .lexer import tokenize
from ..config import Settings
```Nhập khẩu tương đối phụ thuộc vào`__package__`.
Chúng chỉ hoạt động khi mô-đun được thực thi như một phần của gói. Đây là lý do tại sao việc chạy mô-đun gói trực tiếp dưới dạng tập lệnh có thể phá vỡ quá trình nhập tương đối.
Ví dụ:```text
project/
app/
__init__.py
main.py
config.py
```Bên trong`main.py`:
```python
from .config import Settings
```Điều này hoạt động khi được thực thi với:```bash
python -m app.main
```Nó có thể thất bại khi được thực thi như:```bash
python app/main.py
```bởi vì việc thực thi tập lệnh trực tiếp sẽ thay đổi cách đặt tên và đóng gói mô-đun.
## 40,19`__main__`Mô-đun được thực thi làm điểm vào chương trình được đặt tên`__main__`.
```python
print(__name__)
```Khi chạy dưới dạng tập lệnh:```bash
python script.py
```Đầu ra:```text
__main__
```Khi nhập khẩu:```python
import script
```tên mô-đun là:```text
script
```Đây là lý do tại sao các chương trình Python thường sử dụng:```python
def main():
...
if __name__ == "__main__":
main()
```Bộ phận bảo vệ ngăn không cho mã nhập chương trình chạy trong quá trình nhập.
## 40.20 Chạy mô-đun với`-m`Lệnh:```bash
python -m package.module
```chạy mô-đun theo tên nhập thay vì theo đường dẫn tệp.
Điều này quan trọng vì mô-đun có ngữ cảnh gói chính xác.
Đối với mã gói, ưu tiên:```bash
python -m package.module
```qua:```bash
python package/module.py
```các`-m`form cho phép hoạt động nhập tương đối vì CPython biết gói của mô-đun.
## 40.21 Tệp bộ nhớ đệm mã byte
CPython có thể lưu trữ mã byte được biên dịch trong`__pycache__`.
Ví dụ:```text
package/
module.py
__pycache__/
module.cpython-312.pyc
```Bộ đệm mã byte tránh biên dịch lại nguồn trong mỗi lần nhập khi bộ đệm hợp lệ.
MỘT`.pyc`tập tin chứa:```text
magic number
cache metadata
marshaled code object
```Số ma thuật xác định phiên bản định dạng mã byte. Nó thay đổi khi CPython thay đổi mã byte không tương thích.
CPython kiểm tra tính hợp lệ của bộ đệm bằng cách vô hiệu hóa dựa trên dấu thời gian hoặc dựa trên hàm băm, tùy thuộc vào cách tạo tệp.
## 40.22 Mô-đun nguồn và đối tượng mã
Đối với một người bình thường`.py`mô-đun, trình tải sẽ biên dịch nguồn thành một đối tượng mã.
Sau đó, nó thực thi đối tượng mã trong từ điển mô-đun.
Về mặt khái niệm:```python
module = types.ModuleType("example")
code = compile(source_text, filename, "exec")
exec(code, module.__dict__)
```Điều này gần giống với mô hình thực, mặc dù hệ thống nhập thực tế xử lý nhiều chi tiết hơn.
Điểm quan trọng là việc thực thi mô-đun là thực thi mã thông thường với từ điển mô-đun làm không gian tên chung.
## 40.23 Mô-đun tích hợp
Các mô-đun tích hợp được biên dịch thành thời gian chạy được liên kết hoặc thực thi CPython.
Ví dụ thường bao gồm:```python
import sys
import builtins
import time
```Tính khả dụng của mô-đun tích hợp phụ thuộc vào nền tảng và cấu hình bản dựng.
Một mô-đun tích hợp không yêu cầu định vị một`.py`tài liệu. Trình tải của nó khởi tạo nó từ các định nghĩa cấp độ C.
Bạn có thể kiểm tra tên mô-đun tích hợp:```python
import sys
print(sys.builtin_module_names)
```Các mô-đun tích hợp sẵn cung cấp các dịch vụ thời gian chạy cốt lõi cần thiết trước khi có sẵn hệ thống nhập dựa trên tệp đầy đủ.
## 40.24 Mô-đun đông lạnh
Mô-đun cố định là các mô-đun Python được nhúng vào mã nhị phân CPython dưới dạng mã byte cố định hoặc dữ liệu tĩnh tương đương.
Chúng giúp máy nhập bootstrap trước khi hệ thống nhập hệ thống tệp hoạt động đầy đủ.
Điều này tạo ra một vấn đề khi khởi động:```text
importlib implements imports
but importlib itself must be imported
so parts of importlib are frozen
```Các mô-đun cố định giải quyết chu trình này bằng cách cung cấp mã Python đã chọn mà không cần nhập tệp nguồn thông thường.
## 40.25 Mô-đun mở rộng
Các mô-đun mở rộng là các thư viện chia sẻ gốc được tải vào CPython.
Trên các hệ thống giống Unix, đây thường là`.so`tập tin. Trên Windows, chúng thường`.pyd`tập tin.
Ví dụ nhập khẩu:```python
import _sqlite3
import _ssl
import _hashlib
```Mô-đun mở rộng cung cấp chức năng khởi tạo được gọi bởi CPython. Các mô-đun mở rộng hiện đại sử dụng khởi tạo nhiều pha khi có thể.
Các mô-đun mở rộng phải tuân theo các quy tắc API C của CPython:```text
create module object
define methods
manage reference ownership
set exceptions on failure
return initialized module
```Vì các mô-đun mở rộng chạy mã gốc bên trong quy trình Python nên lỗi có thể làm hỏng trình thông dịch.
## 40.26 Khởi tạo một pha và nhiều pha
Các mô-đun mở rộng cũ hơn thường sử dụng khởi tạo một pha. Hàm khởi tạo tạo và trả về một đối tượng mô-đun trong một bước.
Các mô-đun mở rộng hiện đại có thể sử dụng khởi tạo nhiều giai đoạn. Trong mô hình này, việc tạo mô-đun và thực thi mô-đun được tách biệt.
Điều này phù hợp hơn với ngữ nghĩa nhập cấp độ Python và hỗ trợ trạng thái trên mỗi mô-đun rõ ràng hơn.
Khởi tạo nhiều giai đoạn rất quan trọng đối với:```text
subinterpreter compatibility
module reloading behavior
cleaner module state
avoiding process-global mutable state
future isolation improvements
```Tiện ích mở rộng C lưu trữ tất cả trạng thái trong biến C toàn cục có thể hoạt động trong các trường hợp đơn giản, nhưng nó có thể hoạt động kém với trình thông dịch phụ hoặc khởi tạo lặp lại.
## 40.27 Khóa nhập
Nhập khẩu cần khóa.
Nếu không khóa, hai luồng có thể cố gắng nhập và khởi tạo cùng một mô-đun cùng một lúc.
Hệ thống nhập sử dụng các khóa để đảm bảo rằng một mô-đun không được nhiều luồng thực thi đồng thời theo những cách không an toàn.
Điều này đặc biệt quan trọng khi việc khởi tạo mô-đun có tác dụng phụ:```python
# database.py
connection_pool = create_pool()
```Nếu hai luồng được nhập đồng thời mô-đun này mà không khóa, chúng có thể tạo trạng thái chung trùng lặp hoặc quan sát quá trình khởi tạo một phần.
Khóa nhập ngăn cản nhiều cuộc đua như vậy.
## 40.28 Tải lại mô-đun
Python có thể tải lại mô-đun bằng cách sử dụng`importlib.reload`.
```python
import importlib
import config
importlib.reload(config)
```Việc tải lại sẽ thực thi lại mã mô-đun bằng cách sử dụng đối tượng mô-đun hiện có.
Điều này có những hậu quả tinh tế.
Giả định`config.py`ban đầu chứa:```python
value = 1
```Sau khi chỉnh sửa thành:```python
value = 2
```tải lại bản cập nhật`config.value`.
Nhưng các tài liệu tham khảo hiện có ở nơi khác vẫn có thể trỏ đến các đối tượng cũ.```python
from config import value
import config
import importlib
importlib.reload(config)
print(value) # old binding
print(config.value) # new module attribute
```Tải lại rất hữu ích cho các công cụ phát triển, sổ ghi chép và hệ thống plugin nhưng nó không phải là quá trình thiết lập lại toàn bộ quy trình.
## 40.29 Tác dụng phụ khi nhập khẩu
Tác dụng phụ nhập khẩu thường là nguồn gốc của hành vi khó hiểu.
Mô-đun này có tác dụng phụ rõ ràng:```python
# noisy.py
print("imported noisy")
```Mô-đun này có tác dụng phụ ẩn:```python
# registry.py
handlers = {}
def register(name, fn):
handlers[name] = fn
# plugin.py
from registry import register
def handle(x):
return x
register("plugin", handle)
```Nhập khẩu`plugin`biến đổi`registry.handlers`.
Mẫu này phổ biến trong các hệ thống plugin, ORM, khung kiểm tra và khung web. Nó có thể hữu ích, nhưng nó có nghĩa là lệnh nhập sẽ trở thành một phần của hành vi chương trình.
## 40.30 Nhập khẩu lười biếng
Quá trình nhập chậm sẽ trì hoãn việc nhập mô-đun cho đến khi cần.
Ví dụ:```python
def parse_json(text):
import json
return json.loads(text)
```Điều này có thể giảm thời gian khởi động hoặc tránh sự phụ thuộc tùy chọn trong các đường dẫn mã không sử dụng.
Nhưng việc nhập khẩu lười biếng có sự đánh đổi:```text
errors appear later
first call may become slower
dependency structure becomes less visible
circular imports may be hidden rather than fixed
```Nhập khẩu lười biếng rất hữu ích khi được sử dụng có chủ ý. Chúng không nên trở thành giải pháp mặc định cho cấu trúc mô-đun kém.
## 40.31 Nhập khẩu tùy chọn
Nhập tùy chọn là phổ biến để phát hiện tính năng.```python
try:
import uvloop
except ImportError:
uvloop = None
```Hãy cẩn thận với việc xử lý ngoại lệ rộng. Điều này thường sai:```python
try:
import plugin
except Exception:
plugin = None
```Nó giấu những lỗi thực sự bên trong`plugin`.
Thích bắt hơn`ImportError`hoặc`ModuleNotFoundError`trong phạm vi hẹp và khi cần, hãy xác minh xem mô-đun nào bị lỗi.```python
try:
import optional_backend
except ModuleNotFoundError as exc:
if exc.name != "optional_backend":
raise
optional_backend = None
```Điều này tránh việc ẩn các phụ thuộc bắc cầu bị thiếu.
## 40.32 Liên kết tên nhập
Các hình thức nhập khác nhau liên kết các tên khác nhau.
| Tuyên bố | Tên ràng buộc |
|---|---|
|`import os` | `os` |
| `import os.path` | `os` |
| `import os.path as p` | `p` |
| `from os import path` | `path` |
| `from math import sqrt as s` | `s` |
| `from module import *`| nhiều tên |
Hệ thống nhập tải các mô-đun. Câu lệnh nhập sau đó liên kết các tên trong không gian tên hiện tại.
Đây là những hoạt động liên quan nhưng riêng biệt.
## 40,33 Sao Nhập khẩu
Việc nhập dấu sao sẽ sao chép các tên đã xuất vào không gian tên hiện tại.```python
from module import *
```Nếu mô-đun xác định`__all__`, Python nhập những tên đó.```python
__all__ = ["connect", "close"]
```Không có`__all__`, Python nhập các tên không bắt đầu bằng dấu gạch dưới.
Việc nhập sao thường không được khuyến khích bên ngoài các phiên tương tác và mô-đun mặt tiền gói vì chúng che khuất tên đến từ đâu.
Mặt tiền gói được kiểm soát có thể sử dụng chúng một cách cẩn thận:```python
# package/__init__.py
from .client import Client
from .errors import PackageError
__all__ = ["Client", "PackageError"]
```## 40.34 Mặt tiền trọn gói
Một gói có thể xuất lại tên từ các mô-đun con.```python
# library/__init__.py
from .client import Client
from .config import Config
__all__ = ["Client", "Config"]
```Sau đó người dùng có thể viết:```python
from library import Client
```thay vì:```python
from library.client import Client
```Điều này cải thiện tính tiện dụng của API nhưng có thể làm tăng chi phí nhập khẩu. Nếu như`library.__init__`nhập nhiều mô-đun con nặng, sau đó`import library`trở nên đắt đỏ.
Mặt tiền gói tốt cân bằng giữa sự thuận tiện và chi phí khởi động.
## 40,35 Hiệu suất nhập khẩu
Thời gian nhập rất quan trọng đối với các công cụ dòng lệnh, khởi động nguội máy chủ, kiểm tra và tập lệnh chạy ngắn.
Chi phí nhập khẩu đến từ:```text
file-system searches
source decoding
bytecode validation
compilation if cache is missing
module execution
transitive imports
native extension loading
top-level initialization
```Bạn có thể kiểm tra thời gian nhập bằng:```bash
python -X importtime -c "import your_package"
```Điều này in một cây thời gian nhập.
Các cách phổ biến để cải thiện hiệu suất nhập khẩu:```text
avoid heavy top-level work
delay optional imports
reduce large dependency chains
avoid importing test-only modules at runtime
keep package __init__.py small
avoid broad convenience imports in hot paths
```## 40.36 Lỗi nhập
Các trường hợp ngoại lệ phổ biến liên quan đến nhập khẩu là:
| Ngoại lệ | Ý nghĩa |
|---|---|
|`ModuleNotFoundError`| Không thể tìm thấy mô-đun được yêu cầu |
|`ImportError`| Nhập không thành công vì lý do rộng hơn |
|`AttributeError`| Đã tải mô-đun nhưng thuộc tính được yêu cầu không tồn tại |
|`SyntaxError`| Không thể biên dịch mô-đun nguồn |
| Lỗi tải gốc | Không tải được mô-đun mở rộng |
Ví dụ:```python
from package import missing_name
```Nếu như`package`tồn tại nhưng`missing_name`không, Python có thể tăng`ImportError`.
Ví dụ:```python
import missing_package
```Thường tăng:```text
ModuleNotFoundError
```Chẩn đoán lỗi nhập khẩu cần phân biệt:```text
the target module is missing
a transitive dependency is missing
the module exists but raised during execution
the requested exported name is missing
a native extension failed to load
```## 40.37 Nhập khẩu tuần hoàn
Quá trình nhập tuần hoàn xảy ra khi các mô-đun phụ thuộc vào nhau trong quá trình thực thi cấp cao nhất.
Ví dụ:```python
# users.py
from posts import Post
class User:
...
# posts.py
from users import User
class Post:
...
```Điều này có thể thất bại vì mỗi mô-đun cần mô-đun kia trước khi khởi tạo xong.
Các cách sửa lỗi phổ biến:
1. Chuyển các loại chia sẻ sang mô-đun thứ ba.```text
models/
base.py
users.py
posts.py
```2. Sử dụng tính năng nhập cục bộ cho các phần phụ thuộc chỉ trong thời gian chạy.```python
def create_post():
from posts import Post
return Post()
```3. Sử dụng chú thích loại bị trì hoãn.```python
from __future__ import annotations
class User:
posts: list[Post]
```4. Phụ thuộc vào giao diện hơn là các mô-đun cụ thể.
Cách khắc phục tốt nhất thường là về mặt cấu trúc. Nhập khẩu tuần hoàn thường tiết lộ rằng ranh giới mô-đun được lựa chọn kém.
## 40.38 Kiểm tra nhập và loại
Gợi ý loại có thể tạo chu kỳ nhập nếu chú thích nhập đối tượng thời gian chạy.
Một mô hình phổ biến là:```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from posts import Post
class User:
def add_post(self, post: "Post") -> None:
...
TYPE_CHECKINGlà sai trong thời gian chạy, do đó quá trình nhập sẽ hiển thị với trình kiểm tra loại nhưng bị bỏ qua trong khi thực thi.
Điều này làm giảm chu kỳ nhập thời gian chạy trong khi vẫn giữ thông tin loại.
Python hiện đại cũng hỗ trợ đánh giá chú thích bị trì hoãn trong một số ngữ cảnh, điều này làm giảm hơn nữa việc nhập thời gian chạy để gõ.
40.39 Móc nhập khẩu
Móc nhập cho phép chương trình tùy chỉnh hành vi nhập.
Một công cụ tìm tùy chỉnh có thể được đặt trênsys.meta_path.
Trình tải tùy chỉnh có thể tạo và thực thi các mô-đun từ các nguồn không chuẩn.
Các trường hợp sử dụng bao gồm:text zip import plugin systems test isolation sandboxed module loading import tracing encrypted module stores remote module stores generated modules Bộ xương công cụ tìm tối thiểu trông giống như:```python
class Finder:
def find_spec(self, fullname, path=None, target=None):
if fullname == "virtual_module":
...
return None
Móc nhập khẩu rất mạnh mẽ. Chúng ảnh hưởng đến hoạt động của chương trình toàn cầu, vì vậy chúng phải có phạm vi hẹp và có thể dự đoán được.
## 40,40`importlib`
`importlib`là giao diện thư viện chuẩn cho hệ thống nhập.
Các thao tác chung:```python
import importlib
mod = importlib.import_module("json")
mod = importlib.reload(mod)
```Các phần cấp thấp hơn hữu ích bao gồm:```text
importlib.util.find_spec
importlib.util.module_from_spec
spec.loader.exec_module
importlib.machinery.PathFinder
importlib.machinery.SourceFileLoader
```Tải thủ công có thể trông như thế này:```python
import importlib.util
spec = importlib.util.spec_from_file_location("custom_name", "/path/to/file.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
```Điều này tạo và thực thi một mô-đun từ một tệp cụ thể.
Đối với mã ứng dụng thông thường, hãy ưu tiên nhập khẩu thông thường. Chỉ sử dụng tính năng tải importlib thủ công khi xây dựng công cụ, hệ thống plugin, trình tải hoặc hệ thống mô-đun thời gian chạy.
## 40.41 Nhận dạng mô-đun
Nhận dạng mô-đun dựa trên khóa trong`sys.modules`.
Nếu cùng một tệp được nhập dưới hai tên khác nhau, CPython có thể tạo hai đối tượng mô-đun riêng biệt.
Vấn đề ví dụ:```text
project/
package/
__init__.py
settings.py
```Nếu một phần của chương trình nhập:```python
import package.settings
```và một nguyên nhân thao túng đường dẫn khác:```python
import settings
```thì cùng một tệp có thể được tải hai lần dưới các tên khác nhau.
Điều đó có thể nhân đôi toàn cầu mô-đun:```text
two registries
two singleton objects
two class identities
two caches
```Đây là một lý do trực tiếp`sys.path`thao tác có thể nguy hiểm.
## 40.42 Mô-đun toàn cầu là trạng thái được chia sẻ
Biến cấp mô-đun được chia sẻ bởi tất cả mã nhập mô-đun.```python
# state.py
count = 0
def increment():
global count
count += 1
return count
```Mọi nhà nhập khẩu đều quan sát cùng một đối tượng mô-đun:```python
import state
state.increment()
state.increment()
```Điều này rất hữu ích cho các hằng số, sổ đăng ký, bộ đệm và singleton. Nó cũng có thể làm cho việc kiểm tra khó khăn hơn vì trạng thái vẫn tồn tại trong các lần nhập khẩu.
Một thử nghiệm có thể cần phải thiết lập lại trạng thái mô-đun một cách rõ ràng:```python
import state
def test_increment():
state.count = 0
assert state.increment() == 1
```Hoặc cô lập hành vi bằng cách tránh các mô-đun toàn cầu có thể thay đổi.
## 40.43 Cấu hình thời gian nhập
Các mô-đun thường đọc cấu hình tại thời điểm nhập:```python
import os
DEBUG = os.environ.get("DEBUG") == "1"
```Điều này làm cho cấu hình được cố định tại thời điểm nhập. Nếu môi trường thay đổi sau này,`DEBUG`không tự động cập nhật.
Thiết kế linh hoạt hơn sẽ đọc cấu hình khi cần:```python
import os
def debug_enabled():
return os.environ.get("DEBUG") == "1"
```hoặc tập trung tải cấu hình:```python
class Settings:
def __init__(self):
self.debug = os.environ.get("DEBUG") == "1"
settings = Settings()
```Cấu hình tại thời điểm nhập rất đơn giản nhưng có thể gây bất ngờ cho các bài kiểm tra và chương trình chạy dài.
## 40.44 Cấp độ mô-đun`__getattr__`Các mô-đun có thể xác định`__getattr__`để tùy chỉnh quyền truy cập thuộc tính cho các tên bị thiếu.```python
# package/__init__.py
def __getattr__(name):
if name == "heavy":
from . import heavy
return heavy
raise AttributeError(name)
```Điều này có thể thực hiện xuất khẩu lười biếng.
Sau đó:```python
import package
package.heavy
```chỉ nhập mô-đun con nặng khi được yêu cầu.
Cấp độ mô-đun`__getattr__`rất hữu ích cho các miếng chêm tương thích, không dùng nữa và tải chậm. Nó vẫn đơn giản vì nó thay đổi hành vi truy cập thuộc tính thông thường.
## 40,45 Cấp độ mô-đun`__dir__`Một module cũng có thể định nghĩa`__dir__`.
```python
def __dir__():
return ["Client", "Config", "connect"]
```Điều này kiểm soát những gì xuất hiện trong:```python
dir(module)
```Nó chủ yếu hữu ích cho các mô-đun có thuộc tính động.
## 40.46 Hệ thống nhập khi khởi động
Trong quá trình khởi động CPython, hệ thống nhập phải được khởi tạo cẩn thận.
Thời gian chạy cần có đủ máy nhập để tải thư viện chuẩn, nhưng phần lớn hệ thống nhập được viết bằng Python.
Trình tự khởi động sử dụng các mô-đun tích hợp và cố định để hiển thị`importlib`.
Về mặt khái niệm:```text
initialize runtime
initialize builtins and sys
initialize frozen importlib bootstrap code
configure import machinery
initialize sys.path
load site if enabled
start executing user code
```Đây là lý do tại sao nội bộ khởi động bị hạn chế hơn so với nhập thời gian chạy thông thường.
## 40,47`site`và thiết lập môi trường
Sau khi khởi tạo máy móc nhập lõi, CPython thường nhập`site`mô-đun trừ khi bị vô hiệu hóa với`-S`.
các`site`mô-đun định cấu hình các đường dẫn nhập bổ sung, bao gồm các thư mục gói trang web.
Nó cũng có thể xử lý:```text
.pth files
user site-packages
virtual environment path adjustments
sitecustomize
usercustomize
```Điều này có nghĩa là môi trường nhập khi khởi động ứng dụng phụ thuộc vào cờ trình thông dịch, môi trường ảo, bố cục cài đặt và các biến môi trường.
## 40.48 Môi trường ảo và nhập khẩu
Môi trường ảo thay đổi nơi Python tìm kiếm các gói đã cài đặt.
Nó thường thay đổi:```text
sys.prefix
sys.exec_prefix
site-packages paths
script entry points
```Tệp nhị phân trình thông dịch có thể được chia sẻ hoặc sao chép, nhưng môi trường nhập sẽ trỏ đến các thư mục gói của môi trường ảo.
Đây là lý do tại sao:```bash
python -m pip install requests
```bên trong một môi trường ảo làm cho`requests`chỉ có thể nhập trong môi trường đó.
Bản thân hệ thống nhập khẩu cũng vậy. Các đường dẫn tìm kiếm khác nhau.
## 40.49 Nhập từ tệp Zip
Python có thể nhập mô-đun từ kho lưu trữ zip nếu kho lưu trữ được bật`sys.path`.
Ví dụ:```bash
python app.zip
```hoặc:```python
import sys
sys.path.insert(0, "modules.zip")
import mymodule
```Nhập Zip sử dụng trình nhập có thể tìm các tệp mô-đun bên trong kho lưu trữ.
Điều này chứng tỏ tại sao việc nhập lại dựa trên mục nhập đường dẫn thay vì chỉ dựa trên thư mục. MỘT`sys.path`mục nhập có thể được xử lý bằng một móc đường dẫn tùy chỉnh.
## 40.50 Móc đường dẫn và Trình nhập đường dẫn
Đối với việc nhập dựa trên đường dẫn, CPython sử dụng các móc nối đường dẫn để chuyển`sys.path`các mục nhập vào các đối tượng nhập khẩu.
Về mặt khái niệm:```text
sys.path entry
↓
sys.path_hooks
↓
path importer
↓
find module spec
```Bộ đệm`sys.path_importer_cache`lưu trữ các đối tượng nhập khẩu cho các mục nhập đường dẫn.```python
import sys
print(sys.path_hooks)
print(sys.path_importer_cache)
```Điều này tránh việc xây dựng lại các đối tượng nhập khẩu nhiều lần.
## 40.51 Mối lo ngại về bảo mật nhập khẩu
Nhập đường dẫn tìm kiếm. Điều đó làm cho trật tự đường dẫn trở nên nhạy cảm về bảo mật.
Nếu thư mục hiện tại xuất hiện trước thư viện chuẩn, một tệp cục bộ có thể ẩn một mô-đun chuẩn.
Ví dụ:```text
project/
json.py
```Sau đó:```python
import json
```có thể nhập khẩu địa phương`json.py`thay vì thư viện chuẩn`json`.
Điều này có thể gây ra lỗi hoặc vấn đề bảo mật.
Các biện pháp phòng thủ:```text
avoid naming files after standard library modules
avoid unsafe sys.path insertion
run applications from expected working directories
use virtual environments
inspect module.__file__ when debugging
prefer python -m package.module for package code
```Để kiểm tra những gì đã được nhập khẩu:```python
import json
print(json.__file__)
```## 40.52 Nhập và thử nghiệm
Các thử nghiệm thường nhấn mạnh hành vi nhập khẩu.
Các vấn đề kiểm tra phổ biến bao gồm:```text
tests depend on working directory
local files shadow installed packages
package imported twice under different names
module global state leaks between tests
environment variables read at import time
plugins register themselves during import
```Thiết lập thử nghiệm mạnh mẽ sẽ nhập gói giống như cách người dùng thực hiện.
Thích thử nghiệm hành vi gói đã cài đặt:```bash
python -m pytest
```từ một môi trường trong sạch, thay vì dựa vào cách bố trí đường dẫn ngẫu nhiên.
## 40.53 Nhập khẩu và thiết kế ứng dụng
Cấu trúc ứng dụng Python tốt giúp giảm độ phức tạp khi nhập.
Một bố cục chung:```text
project/
pyproject.toml
src/
app/
__init__.py
main.py
config.py
service.py
storage.py
tests/
test_service.py
```các`src`bố cục giúp nắm bắt việc nhập ngẫu nhiên từ thư mục gốc của kho lưu trữ.
Biểu đồ phụ thuộc mô-đun rõ ràng hướng vào trong:```text
main
depends on service
service
depends on storage and config
storage
depends on database driver
config
depends on environment parsing
```Tránh các thiết kế trong đó các mô-đun cấp thấp nhập các điểm vào ứng dụng cấp cao.
## 40.54 Nhập khẩu và thiết kế API công khai
Bề mặt nhập của gói là một phần của API công khai.
Ví dụ:```python
from library import Client
```là một hợp đồng công cộng nếu được ghi lại.
Việc thay đổi vị trí mô-đun nội bộ sẽ không làm gián đoạn người dùng nếu mặt tiền gói duy trì quá trình nhập công khai:```python
# library/__init__.py
from ._client import Client
__all__ = ["Client"]
```Các mô-đun riêng tư thường sử dụng dấu gạch dưới ở đầu:```text
library/
__init__.py
_client.py
_protocol.py
public.py
```Đây là một quy ước, không phải là một hạn chế truy cập.
## 40.55 Danh sách kiểm tra gỡ lỗi nhập
Khi hành vi nhập khó hiểu, hãy kiểm tra các giá trị sau:```python
import sys
import module
print(module)
print(module.__name__)
print(getattr(module, "__file__", None))
print(getattr(module, "__spec__", None))
print(getattr(module, "__package__", None))
print(sys.path)
```Đối với các vấn đề về gói:```python
import package
print(package.__path__)
print(package.__spec__.submodule_search_locations)
```Đối với các vấn đề về bộ đệm:```python
import sys
print(sys.modules.get("module_name"))
```Đối với thời gian:```bash
python -X importtime -c "import module_name"
```Để giải quyết trực tiếp:```python
import importlib.util
print(importlib.util.find_spec("module_name"))
```## 40.56 Thuật toán nhập tối thiểu
Một hàm nhập đơn giản có thể được viết là:```python
def import_module(fullname):
if fullname in sys.modules:
return sys.modules[fullname]
spec = find_spec(fullname)
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
return module
```Hệ thống nhập CPython thực sự phức tạp hơn, nhưng khung này nắm bắt được luồng trung tâm:```text
cache lookup
spec discovery
module creation
cache insertion
module execution
error cleanup
return module
```## 40.57 Lỗi thường gặp: Mô-đun được khởi tạo một phần
Một lỗi phổ biến trông giống như:```text
AttributeError: partially initialized module 'x' has no attribute 'y'
```Điều này thường có nghĩa là sự cố nhập vòng tròn hoặc mô-đun bị che khuất.
Ví dụ nhập vòng tròn:```python
# a.py
import b
class A:
...
# b.py
import a
class B(a.A):
...
```Khi`b`đọc`a.A`, mô-đun`a`tồn tại ở`sys.modules`, nhưng lớp của nó`A`vẫn chưa được xác định.
Cách khắc phục thường là cơ cấu lại các mô-đun để các định nghĩa lớp không yêu cầu nhập lẫn nhau trong quá trình thực thi cấp cao nhất.
## 40.58 Lỗi thường gặp: Shadowing
Nếu một tệp được đặt tên theo mô-đun thư viện tiêu chuẩn, quá trình nhập có thể giải quyết sai tệp.
Ví dụ:```text
random.py
```Bên trong nó:```python
import random
```Điều này có thể tự nhập thay vì thư viện chuẩn`random`.
Các triệu chứng bao gồm:```text
partially initialized module
missing expected attributes
recursive import behavior
strange module.__file__
```Kiểm tra:```python
import random
print(random.__file__)
```Đổi tên tệp cục bộ và xóa các tệp bộ đệm cũ nếu cần.
## 40.59 Lỗi thường gặp: Chạy trực tiếp tệp gói
Cho:```text
app/
__init__.py
main.py
config.py
```Bên trong`main.py`:
```python
from .config import Settings
```Điều này có thể thất bại:```bash
python app/main.py
```bởi vì`main.py`được thực thi như`__main__`, không phải như`app.main`.
Sử dụng:```bash
python -m app.main
```từ thư mục chứa`app`.
Điều này bảo tồn bối cảnh gói và làm cho việc nhập tương đối hoạt động.
## 40,60 Điểm chính
Mô-đun là một đối tượng thời gian chạy có từ điển không gian tên.
Việc nhập mô-đun sẽ thực thi mã cấp cao nhất của nó một lần cho mỗi tên mô-đun trong`sys.modules`.
Hệ thống nhập được xây dựng dựa trên công cụ tìm, trình tải, thông số mô-đun, móc nối đường dẫn và bộ đệm.
Các gói là các mô-đun có vị trí tìm kiếm mô-đun con.
Nhập vòng tròn làm lộ các mô-đun được khởi tạo một phần vì CPython chèn các mô-đun vào`sys.modules`trước khi thực hiện.
Hệ thống nhập khẩu được lập trình thông qua`importlib`, `sys.meta_path`, móc đường dẫn và bộ tải.
Hầu hết các vấn đề về nhập đều xuất phát từ sự phụ thuộc vòng tròn, hiện tượng che khuất đường dẫn, thực thi trực tiếp các tệp gói, tác dụng phụ của thời gian nhập hoặc nhận dạng mô-đun trùng lặp.