Điều thú vị nhất đang xảy ra ngay bây giờ trong quá trình phát triển Python là gõ tĩnh. Kể từ Python 3. 0, chúng tôi đã có các chú thích chức năng và kể từ 3. 6, biến chú thích. Trong 3. 5, chúng tôi có một thư viện “đánh máy”, cung cấp các công cụ để mô tả các loại. Bạn đã thấy tôi sử dụng gợi ý loại
def f[x: int] -> int: return x * 5
Bạn có thể đã tự hỏi mình, điều đó làm gì?
f[["hi"]]
['hi', 'hi', 'hi', 'hi', 'hi']
Không. Nó không làm gì trong thời gian chạy, ngoại trừ lưu trữ đối tượng. Và trong Python 3 sắp tới. 11 hoặc 3. 12 [hoặc 3. 7+ với
def f[x: int] -> int: return x * 57], nó thậm chí không lưu trữ đối tượng thực tế, chỉ lưu trữ chuỗi bạn nhập ở đây, vì vậy, mọi thứ có thể vượt qua trình phân tích cú pháp Python đều được phép ở đây
Nó không phải là vô ích mặc dù. Đối với một, nó giúp người đọc. Việc biết các loại được mong đợi thực sự giúp bạn hiểu rõ hơn về những gì đang diễn ra và những gì bạn có thể làm và không thể làm
Nhưng mục tiêu chính là. kiểm tra kiểu tĩnh. Có một bộ sưu tập các trình kiểm tra kiểu tĩnh, trong đó "chính thức" và nổi tiếng nhất là MyPy. Bạn có thể coi đây là “trình biên dịch” cho các ngôn ngữ được biên dịch như C++; . Ví dụ
%%save_and_run mypy def f[x: int] -> int: return x * 5 f[["hi"]]
tmp.py:4: error: Argument 1 to "f" has incompatible type "List[str]"; expected "int" Found 1 error in 1 file [checked 1 source file]
chúng ta đi thôi. Và quan trọng nhất, chúng tôi không phải chạy bất kỳ mã nào để thấy lỗi này. Các bài kiểm tra của bạn không thể kiểm tra mọi nhánh có thể, mọi dòng mã. MyPy có thể [mặc dù theo mặc định thì không, do gõ dần dần]. Bạn có thể có mã hiếm khi chạy, yêu cầu tài nguyên từ xa, chậm, v.v. Tất cả những thứ đó có thể được kiểm tra bởi MyPy. Nó cũng giữ cho bạn [cũng vậy?] trung thực trong các loại của bạn
10. 1. 1. Đang bắt lỗi#
Hãy xem một ví dụ về lỗi mà MyPy có thể bắt gặp
%%save_and_run mypy from __future__ import annotations # Python 3.7+ def f[x: int | None] -> int | None: return x * 5 f[4]
tmp.py:5: error: Unsupported operand types for * ["None" and "int"] tmp.py:5: note: Left operand is of type "Optional[int]" Found 1 error in 1 file [checked 1 source file]
Bộ thử nghiệm của bạn có thể đã quên chạy với đầu vào
def f[x: int] -> int: return x * 58. Bạn không được gặp
def f[x: int] -> int: return x * 58 thường xuyên, cho đến khi bạn rơi vào tình huống nguy cấp. Nhưng MyPy có thể tìm thấy nó và cho bạn biết có vấn đề về logic, chức năng của bạn không thể mất
def f[x: int] -> int: return x * 58 như tuyên bố của nó
10. 1. 2. Thêm loại#
Có ba cách để thêm các loại
Chúng có thể là nội tuyến dưới dạng chú thích. Tốt nhất cho mã Python 3, thường là
Họ có thể ở trong "loại bình luận" đặc biệt. Hầu hết được yêu cầu đối với mã Python 2 và vẫn yêu cầu nhập đúng cách [một lý do tại sao phần đóng gói lại quan trọng như vậy, tùy thuộc vào các thư viện như backport có quan trọng không]
Chúng có thể nằm trong một tệp riêng có cùng tên nhưng có phần mở rộng
f[["hi"]]
1. Điều này rất quan trọng đối với sơ khai loại hoặc đối với trường hợp bạn không muốn thêm nhập hoặc chạm vào mã gốc. Bạn có thể chú thích các tệp hoặc thư viện đã biên dịch mà bạn không kiểm soát theo cách này
Nếu bạn có một thư viện mà bạn không kiểm soát, bạn có thể thêm “type stubs” cho nó, sau đó cung cấp cho MyPy thư mục stubs của bạn. MyPy sẽ lấy các loại từ sơ khai của bạn. Ví dụ: nếu bạn đang viết mã cho Raspberry Pi, bạn có thể thêm sơ khai cho các thư viện Pi, sau đó xác thực mã của mình mà không cần cài đặt các thư viện chỉ dành cho Pi
10. 1. 3. Cấu hình#
Theo mặc định, MyPy thực hiện càng ít càng tốt để bạn có thể thêm nó lặp đi lặp lại vào cơ sở mã. Theo mặc định
Tất cả các biến chưa được nhập và giá trị trả về sẽ là Bất kỳ
Mã bên trong các chức năng chưa nhập hoàn toàn không được kiểm tra
Bạn có thể thêm cấu hình vào
f[["hi"]]2 [và một chút vào chính các tệp] hoặc bạn có thể thực hiện mọi cách và vượt qua
f[["hi"]]3, thao tác này sẽ bật mọi thứ
Để một thư viện hỗ trợ nhập liệu, nó phải a] thêm các loại bằng cách sử dụng bất kỳ phương pháp nào trong ba phương pháp và b] thêm một tệp trống
f[["hi"]]4 để cho biết rằng bạn có thể tìm kiếm các loại bên trong nó. MyPy cũng xem trong
f[["hi"]]5, đây là thư viện có đầy đủ các gợi ý về loại cho [hầu hết] thư viện chuẩn
Nhân tiện, các thư viện của bên thứ ba được nhập đôi khi quên bước cuối cùng này
Cá nhân tôi khuyên bạn nên sử dụng pre-commit để chạy tất cả các kiểm tra của bạn ngoại trừ pytest [và điều đó chỉ vì nó có thể chậm] và bao gồm cả MyPy trong thử nghiệm pre-commit của bạn. Cố gắng bật càng nhiều càng tốt và tăng dần cho đến khi bạn có thể chạy với đầy đủ
f[["hi"]]6 kiểm tra
10. 1. 4. Các tính năng khác#
Gõ tĩnh có một số tính năng tuyệt vời đáng để kiểm tra
Công đoàn [Cú pháp mới trong Python 3. 10]
Các loại chung [Cú pháp mới trong Python 3. 9]
giao thức
chữ
đã gõDict
Định nghĩa NamedTuple đẹp hơn [rất phổ biến trong mã Python 3]
MyPy xác thực phiên bản Python mà bạn yêu cầu
10. 2. Ví dụ mở rộng#
Đây là cú pháp cổ điển mà bạn cần sử dụng nếu hỗ trợ 3. 6+
f[["hi"]]7
f[["hi"]]8
Với
def f[x: int] -> int: return x * 57 trong Python 3. 7, các chú thích không còn được đánh giá trong thời gian chạy và do đó, điều này hợp lệ trên Python 3. 7 và MyPy
def f[x: int] -> int: return x * 50
f[["hi"]]8
Lưu ý rằng tôi thậm chí không phải nhập bất cứ thứ gì từ việc gõ. Lưu ý rằng bạn không thể sử dụng cú pháp “mới” ở những vị trí không có chú thích [như liên kết trong
f[["hi"]]8] cho đến khi Python hỗ trợ nó
Bạn có thể sử dụng phần trên trong các phiên bản Python cũ hơn nếu bạn sử dụng chuỗi
Khi chạy cùng với một kẻ nói dối giỏi như flake8, điều này có thể phát hiện ra rất nhiều vấn đề trước khi kiểm tra hoặc chúng được phát hiện ngoài tự nhiên. Nó cũng nhắc thiết kế tốt hơn, bởi vì bạn đang suy nghĩ về cách các loại hoạt động và tương tác. Nó cũng dễ đọc hơn, vì nếu tôi cung cấp cho bạn mã như thế này
def f[x: int] -> int: return x * 52
Bạn không biết dấu thời gian "là gì". Nó là một int? . Bạn có thể sử dụng bí danh loại để thực sự đặt tên biểu cảm ở đây
10. 3. Giao thức#
Một trong những tính năng tốt nhất của MyPy là hỗ trợ phân nhóm cấu trúc thông qua Giao thức - về cơ bản là kiểu gõ vịt được chính thức hóa. Điều này cho phép khả năng tương tác giữa các thư viện, không giống như kế thừa truyền thống. Đây là cách nó hoạt động
def f[x: int] -> int: return x * 53
Vâng,
f[["hi"]]9 thực sự là một phần của mã ở đây;
Giờ đây, bất kỳ đối tượng nào có thể “quack” [và trả về một chuỗi] đều là Vịt. Chúng tôi thậm chí có thể thêm
['hi', 'hi', 'hi', 'hi', 'hi']1, điều này sẽ cho phép chúng tôi kiểm tra điều này [trừ các loại] khi chạy trong
f[["hi"]]8. Vì vậy, bây giờ chúng ta có thể thiết kế mã như thế này
def f[x: int] -> int: return x * 54
Và trình kiểm tra loại sẽ đảm bảo chúng tôi chỉ viết mã hợp lệ trên tất cả các “Vịt”. Và, chúng ta có thể viết triển khai duck và kiểm tra nó như thế này
def f[x: int] -> int: return x * 55
Điều này sẽ vượt qua kiểm tra để trở thành Vịt, ví dụ như thế này
def f[x: int] -> int: return x * 56
Lưu ý hoàn toàn thiếu phụ thuộc ở đây. Chúng tôi không cần
['hi', 'hi', 'hi', 'hi', 'hi']3 để viết
['hi', 'hi', 'hi', 'hi', 'hi']4 hoặc ngược lại. Và, chúng tôi thậm chí không cần
['hi', 'hi', 'hi', 'hi', 'hi']5 để viết một trong hai trong thời gian chạy. Sự phụ thuộc vào Vịt cho
['hi', 'hi', 'hi', 'hi', 'hi']4 hoàn toàn là sự phụ thuộc kiểu-kiểm tra-thời gian [trừ khi chúng tôi muốn sử dụng một
f[["hi"]]8 được cung cấp bởi
['hi', 'hi', 'hi', 'hi', 'hi']7]
Có rất nhiều Giao thức tích hợp, hầu hết trong số đó được nhập trước ngày và có sẵn ở dạng Lớp cơ sở trừu tượng. Hầu hết trong số họ kiểm tra một hoặc nhiều phương thức đặc biệt, như Iterable, Iterator, v.v.