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

Python property decorator là gì? Khám phá ngay

21/11/2023 01:56

Lập trình Python cung cấp cho chúng ta trình trang trí @property tích hợp sẵn giúp việc sử dụng getter và setters dễ dàng hơn nhiều trong Lập trình hướng đối tượng.

 

 

Lập trình Python cung cấp cho chúng ta trình trang trí @property tích hợp sẵn giúp việc sử dụng getter và setters dễ dàng hơn nhiều trong Lập trình hướng đối tượng.

Trước khi đi vào chi tiết về @property trang trí là gì, trước tiên chúng ta hãy xây dựng trực giác về lý do tại sao nó lại cần thiết ngay từ đầu.

Lớp không có Getters và Setters

Giả sử rằng chúng ta quyết định tạo một lớp lưu trữ nhiệt độ theo độ C. Và, nó cũng sẽ thực hiện một phương pháp chuyển đổi nhiệt độ thành độ F.

Một cách để làm điều này là như sau:

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

Chúng ta có thể tạo các đối tượng từ lớp này và thao tác thuộc tính temperature theo ý muốn:

# Basic method of setting and getting attributes in Python
class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32


# Create a new object
human = Celsius()

# Set the temperature
human.temperature = 37

# Get the temperature attribute
print(human.temperature)

# Get the to_fahrenheit method
print(human.to_fahrenheit())

Output:

37
98.60000000000001

Ở đây, số chữ số thập phân thừa khi chuyển đổi sang Fahrenheit là do Lỗi số học dấu phẩy động.

Vì vậy, bất cứ khi nào chúng ta gán hoặc truy xuất bất kỳ thuộc tính đối tượng nào như temperature như được hiển thị ở trên, Python sẽ tìm kiếm nó trong __dict__ thuộc tính từ điển dưới dạng

print(human.__dict__) 
# Output: {'temperature': 37}

Do đó, human.temperature bên trong trở thành human.__dict__['temperature'].


Sử dụng Getters và Setters

Giả sử chúng ta muốn mở rộng khả năng sử dụng củađộ CLớp được định nghĩa ở trên. Chúng ta biết rằng nhiệt độ của bất kỳ vật thể nào cũng không thể xuống dưới -273,15 độ C.

Hãy cập nhật mã của chúng tôi để triển khai ràng buộc giá trị này.

Một giải pháp rõ ràng cho hạn chế trên sẽ là ẩn thuộc tính temperature (đặt nó ở chế độ riêng tư) và xác định các phương thức getter và setter mới để thao tác với nó.

Điều này có thể được thực hiện như sau:

# Making Getters and Setter methods
class Celsius:
    def __init__(self, temperature=0):
        self.set_temperature(temperature)

    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32

    # getter method
    def get_temperature(self):
        return self._temperature

    # setter method
    def set_temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible.")
        self._temperature = value

Như chúng ta có thể thấy, phương thức trên giới thiệu hai phương thức get_temperature() và set_temperature() mới.

Hơn nữa, temperature đã được thay thế bằng _temperature. Dấu gạch dưới _ ở đầu được dùng để biểu thị các biến riêng tư trong Python.

Bây giờ, hãy sử dụng triển khai này:

 
# Making Getters and Setter methods
class Celsius:
    def __init__(self, temperature=0):
        self.set_temperature(temperature)

    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32

    # getter method
    def get_temperature(self):
        return self._temperature

    # setter method
    def set_temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible.")
        self._temperature = value


# Create a new object, set_temperature() internally called by __init__
human = Celsius(37)

# Get the temperature attribute via a getter
print(human.get_temperature())

# Get the to_fahrenheit method, get_temperature() called by the method itself
print(human.to_fahrenheit())

# new constraint implementation
human.set_temperature(-300)

# Get the to_fahreheit method
print(human.to_fahrenheit())

đầu ra

37
98,60000000000001
Traceback (cuộc gọi gần đây nhất):
  Tệp "<chuỗi>", dòng 30, trong <module>
  Tệp "<chuỗi>", dòng 16, trong set_ Nhiệt độ
ValueError: Không thể thực hiện được nhiệt độ dưới -273,15.

Bản cập nhật này đã triển khai thành công hạn chế mới. Chúng tôi không còn được phép đặt nhiệt độ dưới -273,15 độ C nữa.

Lưu ý: Các biến riêng tư không thực sự tồn tại trong Python. Đơn giản là có những quy tắc phải được tuân theo. Bản thân ngôn ngữ không áp dụng bất kỳ hạn chế nào.

Tuy nhiên, vấn đề lớn hơn với bản cập nhật trên là tất cả các chương trình triển khai lớp trước của chúng tôi đều phải sửa đổi mã của chúng từ obj.temperature thành obj.get_temperature() và tất cả các biểu thức như obj.temperature = val đến obj.set_temperature(val).

Việc tái cấu trúc này có thể gây ra sự cố khi xử lý hàng trăm nghìn dòng mã.

Nói chung, bản cập nhật mới của chúng tôi không tương thích ngược. Đây là lúc @property đến giải cứu.


Lớp tài sản

Một cách Pythonic để giải quyết vấn đề trên là sử dụng lớp property. Đây là cách chúng tôi có thể cập nhật mã của mình:

# using property class
class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    # getter
    def get_temperature(self):
        print("Getting value...")
        return self._temperature

    # setter
    def set_temperature(self, value):
        print("Setting value...")
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        self._temperature = value

    # creating a property object
    temperature = property(get_temperature, set_temperature)

Chúng tôi đã thêm hàm print() bên trong get_temperature() và set_temperature() để quan sát rõ ràng rằng chúng đang được thực thi.

Dòng cuối cùng của mã tạo thành một đối tượng thuộc tính temperature. Nói một cách đơn giản, thuộc tính đính kèm một số mã (get_temperature và set_temperature) để truy cập thuộc tính thành viên (temperature).

Hãy sử dụng mã cập nhật này:

# using property class
class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    # getter
    def get_temperature(self):
        print("Getting value...")
        return self._temperature

    # setter
    def set_temperature(self, value):
        print("Setting value...")
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        self._temperature = value

    # creating a property object
    temperature = property(get_temperature, set_temperature)


human = Celsius(37)

print(human.temperature)

print(human.to_fahrenheit())

human.temperature = -300

đầu ra

Đặt giá trị...
Nhận giá trị...
37
Nhận giá trị...
98.60000000000001
Giá trị cài đặt...
Traceback (cuộc gọi gần đây nhất):
  Tệp "<chuỗi>", dòng 31, trong <module>
  Tệp "<chuỗi>", dòng 18, trong set_ Nhiệt độ
ValueError: Không thể thực hiện được nhiệt độ dưới -273

Như chúng ta có thể thấy, bất kỳ mã nào truy xuất giá trị của temperature sẽ tự động gọi get_temperature() thay vì tra cứu từ điển (__dict__).

Tương tự, bất kỳ mã nào gán giá trị cho temperature sẽ tự động gọi set_temperature().

Chúng ta thậm chí có thể thấy ở trên rằng set_temperature() đã được gọi ngay cả khi chúng ta tạo một đối tượng.

human = Celsius(37) # prints Setting value...

Bạn có thể đoán tại sao không?

Lý do là khi một đối tượng được tạo, phương thức __init__() sẽ được gọi. Phương thức này có dòng self.temperature = temperature. Biểu thức này tự động gọi set_temperature().

Tương tự, mọi quyền truy cập như c.temperature sẽ tự động gọi get_temperature(). Đây là những gì tài sản làm.

Bằng cách sử dụng property, chúng ta có thể thấy rằng không cần sửa đổi gì khi triển khai ràng buộc giá trị. Do đó, việc triển khai của chúng tôi tương thích ngược.

Lưu ý: Giá trị nhiệt độ thực tế được lưu trữ trong biến _temperature riêng tư. Thuộc tính temperature là một đối tượng thuộc tính cung cấp giao diện cho biến riêng tư này.


Công cụ trang trí @property

Trong Python, property() là một hàm dựng sẵn để tạo và trả về một đối tượng property. Cú pháp của hàm này là:

property(fget=None, fset=None, fdel=None, doc=None)

Đây,

  • fgetlà hàm lấy giá trị của thuộc tính
  • fsetlà hàm thiết lập giá trị của thuộc tính
  • fdellà chức năng xóa thuộc tính
  • doclà một chuỗi (như một bình luận)

Như đã thấy từ quá trình triển khai, các đối số hàm này là tùy chọn.

Đối tượng thuộc tính có ba phương thức, getter()setter() và deleter() để chỉ định fgetfset và fdel ở thời điểm sau đó. Điều này có nghĩa là dòng:

temperature = property(get_temperature,set_temperature)

có thể được chia nhỏ như sau:

# make empty property
temperature = property()

# assign fget
temperature = temperature.getter(get_temperature)

# assign fset
temperature = temperature.setter(set_temperature)

Hai đoạn mã này tương đương nhau.

Các lập trình viên quen thuộc với Trình trang trí Python có thể nhận ra rằng cấu trúc trên có thể được triển khai dưới dạng trình trang trí.

Chúng tôi thậm chí không thể xác định tên get_temperature và set_temperature vì chúng không cần thiết và gây ô nhiễm không gian tên lớp.

Đối với điều này, chúng tôi sử dụng lại tên temperature trong khi xác định các hàm getter và setter của mình. Hãy xem cách triển khai tính năng này với tư cách là một công cụ trang trí:

 
# Using @property decorator
class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value...")
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        print("Setting value...")
        if value < -273.15:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value


# create an object
human = Celsius(37)

print(human.temperature)

print(human.to_fahrenheit())

coldest_thing = Celsius(-300)

Out put
Setting value...
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
  File "", line 29, in 
  File "", line 4, in __init__
  File "", line 18, in temperature
ValueError: Temperature below -273 is not possible