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,
fget
là hàm lấy giá trị của thuộc tínhfset
là hàm thiết lập giá trị của thuộc tínhfdel
là chức năng xóa thuộc tínhdoc
là 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 fget
, fset
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