× Giới thiệu Lịch khai giảng Tin tức Sản phẩm học viên

Python's Self Type: Cách chú thích các phương thức trả về self

26/06/2023 10:29

Nếu không sử dụng đúng các gợi ý và Self type , việc khám phá các loại biến có thể trở thành một nhiệm vụ tẻ nhạt và tốn thời gian., Tìm hiểu về Python's Self Type ngay trong bài viết dưới đây!

Bạn đã bao giờ thấy mình bị lạc trong một kho mã Python khổng lồ, cố gắng theo dõi các loại biến dự định chưa? Nếu không sử dụng đúng các gợi ý và Self type , việc khám phá các loại biến có thể trở thành một nhiệm vụ tẻ nhạt và tốn thời gian. Có lẽ bạn là người thích sử dụng gợi ý kiểu nhưng không chắc cách chú thích các phương thức trả về hoặc các phiên bản khác của chính lớp đó. Đó là vấn đề mà bạn sẽ giải quyết trong hướng dẫn này.self

Tuy nhiên, trước tiên, bạn sẽ cần hiểu loại gợi ý là gì và chúng hoạt động như thế nào. Gợi ý kiểu cho phép bạn chỉ rõ một cách rõ ràng kiểu biến, đối số hàm và giá trị trả về. Điều này có thể làm cho mã của bạn dễ đọc và dễ bảo trì hơn, đặc biệt là khi mã phát triển về kích thước và độ phức tạp.

Bạn chỉ định các loại đối số của biến và hàm bằng dấu hai chấm ( :) sau đó là loại dữ liệu , trong khi chú thích giá trị trả về sử dụng dấu gạch ngang–ký hiệu lớn hơn ( ->) sau đó là loại trả về . Để xem một ví dụ, bạn có thể viết một hàm chấp nhận đầu vào là số lượng bánh mà bạn đã mua và giá mỗi chiếc bánh, sau đó xuất ra một chuỗi tóm tắt giao dịch của bạn:

>>>
>>> def buy_pies(num_pies: int, price_per_pie: float) -> str:
...     total_cost = num_pies * price_per_pie
...     return f"Yum! You spent ${total_cost} dollars on {num_pies} pies!"
...

Trong buy_pies(), bạn nhập num_piesbiến với intvà price_per_pievới float. Bạn chú thích giá trị trả về với loại strvì nó trả về một chuỗi.

Các kiểu và chú thích trong Python thường không ảnh hưởng đến chức năng của mã nhưng nhiều trình kiểm tra kiểu tĩnh và IDE nhận ra chúng. Chẳng hạn, nếu bạn di chuột qua buy_pies()trong Mã VS , thì bạn có thể thấy loại của từng đối số hoặc giá trị trả về:

Bạn cũng có thể sử dụng các chú thích khi làm việc với các lớp. Điều này có thể giúp các nhà phát triển khác hiểu được kiểu trả về mong đợi từ một phương thức, điều này có thể đặc biệt hữu ích khi làm việc với hệ thống phân cấp lớp phức tạp. Bạn thậm chí có thể chú thích các phương thức trả về một thể hiện của lớp.

Một trường hợp sử dụng cho các loại và chú thích là chú thích các phương thức trả về một thể hiện của lớp của chúng. Điều này đặc biệt hữu ích cho các phương thức của lớp và có thể ngăn ngừa sự nhầm lẫn phát sinh khi làm việc với kế thừa và xâu chuỗi phương thức. Thật không may, chú thích các phương pháp này có thể gây nhầm lẫn và gây ra các lỗi không mong muốn. Một cách tự nhiên để chú thích một phương thức như vậy là sử dụng tên lớp, nhưng cách sau sẽ không hoạt động:

# incorrect_self_type.py

from typing import Any

class Queue:
    def __init__(self):
        self.items: list[Any] = []

    def enqueue(self, item: Any) -> Queue:
        self.items.append(item)
        return self

Trong ví dụ trên, .enqueue()from Queuenối thêm một mục vào hàng đợi và trả về thể hiện của lớp. .enqueue()Mặc dù việc chú thích bằng tên lớp có vẻ trực quan , nhưng điều này gây ra cả lỗi kiểm tra kiểu tĩnh và thời gian chạy. Hầu hết các trình kiểm tra loại tĩnh đều nhận ra rằng mã đó Queuekhông được xác định trước khi được sử dụng và nếu bạn cố chạy mã, thì bạn sẽ nhận được kết quả như sau NameError:

Traceback (most recent call last):
  ...
NameError: name 'Queue' is not defined

Cũng sẽ có vấn đề khi kế thừa từ Queue. Đặc biệt, một phương thức like .enqueue()sẽ trả về Queuengay cả khi bạn gọi nó trên một lớp con của Queue. Kiểu của Python Selfcó thể xử lý các tình huống này, cung cấp một chú thích nhỏ gọn và dễ đọc, quan tâm đến sự tinh tế của các phương thức chú thích trả về một thể hiện của lớp kèm theo.

Trong hướng dẫn này, bạn sẽ khám phá Selfloại này một cách chi tiết và tìm hiểu cách sử dụng nó để viết mã dễ đọc và dễ bảo trì hơn. Cụ thể, bạn sẽ thấy cách chú thích một phương thức với kiểu Selfvà đảm bảo rằng IDE của bạn sẽ nhận ra điều này. Bạn cũng sẽ kiểm tra các chiến lược thay thế cho các phương thức chú thích trả về một thể hiện của lớp và khám phá lý do tại sao Selfloại này được ưu tiên.

Cách chú thích một phương thức với Self Type trong Python

Do cú pháp ngắn gọn và trực quan của nó, như được định nghĩa bởi PEP 673 , Selfloại này là chú thích ưa thích cho các phương thức trả về một thể hiện của lớp của chúng. Loại này Selfcó thể được nhập trực tiếp từ typingmô-đun của Python trong phiên bản 3.11 trở lên. Đối với các phiên bản Python nhỏ hơn 3.11, Selfloại này có sẵn ở định dạng typing_extensions.

Ví dụ, bạn sẽ chú thích cấu trúc dữ liệu ngăn xếp . Đặc biệt chú ý đến .push()chú thích của bạn:

 1# stack.py
 2
 3from typing import Any, Self
 4
 5class Stack:
 6    def __init__(self) -> None:
 7        self.items: list[Any] = []
 8
 9    def push(self, item: Any) -> Self:
10        self.items.append(item)
11        return self
12
13    def pop(self) -> Any:
14        if self.__bool__():
15            return self.items.pop()
16        else:
17            raise ValueError("Stack is empty")
18
19    def __bool__(self) -> bool:
20        return len(self.items) > 0

Bạn nhập Selfloại từ typingtrong dòng 3 và chú thích .push()bằng -> Selftrong dòng 9. Điều này báo cho các trình kiểm tra loại tĩnh .push()trả về một Stackphiên bản, cho phép bạn tự tin xâu chuỗi nhiều lần đẩy lại với nhau. Lưu ý rằng thường không cần chú thích selfvà clstham số, như đã thảo luận trong PEP 484 .

Đối với các phiên bản Python nhỏ hơn 3.11, bạn có thể sử dụng typing_extensionsmô-đun để nhập Selfloại và bạn có thể giữ nguyên mã còn lại:

# stack.py

from typing import Any
from typing_extensions import Self

# ...

Bằng cách nhập Selftừ typing_extensions, bạn có thể sử dụng Selfđể chú thích các phương thức giống như cách bạn sẽ sử dụng typingmô-đun trong Python 3.11.

Như bạn đã học, .push()hãy thêm các mục vào ngăn xếp và trả về phiên bản ngăn xếp đã cập nhật, yêu cầu Selfchú thích. Điều này cho phép bạn xâu chuỗi tuần tự .push()các phương thức vào một phiên bản ngăn xếp, làm cho mã của bạn ngắn gọn và dễ đọc hơn:

>>>
>>> from stack import Stack
>>> stack = Stack()
>>> stack.push(1).push(2).push(3).pop()
3
>>> stack.items
[1, 2]

Trong ví dụ trên, bạn khởi tạo một Stackthể hiện, đẩy ba phần tử vào ngăn xếp theo trình tự và bật một phần tử. Bằng cách đưa vào Selflàm chú thích, bạn có thể kiểm tra .push()trực tiếp từ một đối tượng được khởi tạo để xem nó trả về cái gì:

Mã VS nhận dạng kiểu trả về của phương thức đẩy
Mã VS nhận ra kiểu trả về của .push()

Khi bạn di chuột qua .push()Mã VS, bạn có thể thấy loại trả về là Stack, như ghi chú chú thích. Với chú thích này, những người khác đọc mã của bạn sẽ không phải xem Stackđịnh nghĩa để biết nó .push()trả về thể hiện của lớp.

Tiếp theo, bạn sẽ xem xét một lớp đại diện cho trạng thái và logic của tài khoản ngân hàng. Lớp BankAccounthỗ trợ một số hành động, chẳng hạn như gửi và rút tiền, cập nhật trạng thái của tài khoản và trả về thể hiện của lớp. Ví dụ: .deposit()lấy một số tiền làm đầu vào, tăng số dư nội bộ của tài khoản và trả về phiên bản để bạn có thể xâu chuỗi các phương thức khác:

# accounts.py

from dataclasses import dataclass
from typing import Self

@dataclass
class BankAccount:
    account_number: int
    balance: float

    def display_balance(self) -> Self:
        print(f"Account Number: {self.account_number}")
        print(f"Balance: ${self.balance:,.2f}\n")
        return self

    def deposit(self, amount: float) -> Self:
        self.balance += amount
        return self

    def withdraw(self, amount: float) -> Self:
        if self.balance >= amount:
            self.balance -= amount
        else:
            print("Insufficient balance")
        return self

Trong BankAccount, bạn chú thích .display_balance().deposit(), và .withdraw()with Selfđể trả về một thể hiện của lớp. Bạn có thể khởi tạo BankAccountvà gửi hoặc rút tiền với số lần tùy ý:

>>>
>>> from accounts import BankAccount
>>> account = BankAccount(account_number=1534899324, balance=50)
>>> (
...     account.display_balance()
...     .deposit(50)
...     .display_balance()
...     .withdraw(30)
...     .display_balance()
... )
Account Number: 1534899324
Balance: $50.00

Account Number: 1534899324
Balance: $100.00

Account Number: 1534899324
Balance: $70.00

BankAccount(account_number=1534899324, balance=70)

Tại đây, bạn xác định một BankAccountphiên bản có số tài khoản và số dư ban đầu. Sau đó, bạn xâu chuỗi nhiều phương thức thực hiện gửi tiền, rút ​​tiền và hiển thị số dư, mỗi phương thức trả về self. REPL tự động in giá trị trả về của biểu thức cuối cùng trong chuỗi phương thức, .display_balance(). Đầu ra của nó, BankAccount(account_number=1534899324, balance=50), cung cấp một biểu diễn đẹp của lớp.

Các trường hợp sử dụng khác cho Selfloại này là các phương thức lớp và phân cấp thừa kế . Chẳng hạn, nếu lớp cha và lớp con của nó có các phương thức trả về self, thì bạn có thể chú thích cả hai bằng Selfkiểu.

Thật thú vị, khi một đối tượng con gọi một phương thức cha mà trả về self, bộ kiểm tra kiểu sẽ chỉ ra rằng phương thức đó trả về một thể hiện của lớp con. Bạn có thể thấy ý tưởng này bằng cách tạo một SavingsAccountlớp kế thừa từ BankAccount:

# accounts.py

import random
from dataclasses import dataclass
from typing import Self

# ...

@dataclass
class SavingsAccount(BankAccount):
    interest_rate: float

    @classmethod
    def from_application(
        cls, deposit: float = 0, interest_rate: float = 1
    ) -> Self:
        # Generate a random seven-digit bank account number
        account_number = random.randint(1000000, 9999999)
        return cls(account_number, deposit, interest_rate)

    def calculate_interest(self) -> float:
        return self.balance * self.interest_rate / 100

    def add_interest(self) -> Self:
        self.deposit(self.calculate_interest())
        return self

SavingsAccountcó một .from_application()phương thức tạo một thể hiện của lớp từ các tham số của ứng viên thay vì thông qua hàm tạo thông thường . Nó cũng có .add_interest(), gửi tiền lãi vào số dư tài khoản và trả về thể hiện của lớp. Bạn có thể tăng khả năng đọc và khả năng bảo trì của cả hai phương pháp này với loại Self.

Tiếp theo, tạo một SavingsAccountđối tượng từ một ứng dụng mới, thực hiện gửi tiền và rút tiền, đồng thời thêm lãi suất:

>>>
>>> from accounts import SavingsAccount
>>> savings = SavingsAccount.from_application(deposit=100, interest_rate=5)
>>> (
...     savings.display_balance()
...     .add_interest()
...     .display_balance()
...     .deposit(50)
...     .display_balance()
...     .withdraw(30)
...     .add_interest()
...     .display_balance()
... )
Account Number: 3631051
Balance: $100.00

Account Number: 3631051
Balance: $105.00

Account Number: 3631051
Balance: $155.00

Account Number: 3631051
Balance: $131.25

SavingsAccount(account_number=3631051, balance=131.25, interest_rate=5)