🔹 Gặp thuộc tính
Chào mừng! Trong bài viết này, bạn sẽ học cách làm việc với @property
trang trí trong Python.
Bạn sẽ học:
- Những lợi thế khi làm việc với các thuộc tính trong Python.
- Khái niệm cơ bản về chức năng trang trí: chúng là gì và chúng liên quan như thế nào đến @property.
- Cách bạn có thể sử dụng @property để xác định getters, setters và deleters.
1️⃣ Ưu điểm của Thuộc tính trong Python
Hãy bắt đầu với một chút bối cảnh. Tại sao bạn có sử dụng các thuộc tính trong Python không?
Các thuộc tính có thể được coi là cách làm việc “Pythonic” với các thuộc tính vì:
- Cú pháp được sử dụng để xác định các thuộc tính rất ngắn gọn và dễ đọc.
- Bạn có thể truy cập các thuộc tính mẫu chính xác như thể chúng là các thuộc tính công khai trong khi sử dụng “phép thuật” của các trung gian (getters và setters) để xác thực các giá trị mới và để tránh truy cập hoặc sửa đổi dữ liệu trực tiếp.
- Bằng cách sử dụng @property, bạn có thể “tái sử dụng” tên của thuộc tính để tránh tạo tên mới cho getters, setters và deleters.
Những lợi thế này làm cho các thuộc tính trở thành một công cụ thực sự tuyệt vời để giúp bạn viết mã ngắn gọn và dễ đọc hơn. ?
2️⃣ Giới thiệu về người trang trí
Một chức năng trang trí về cơ bản là một hàm bổ sung chức năng mới cho một hàm được truyền dưới dạng đối số. Sử dụng chức năng trang trí giống như thêm sô cô la rắc vào kem?. Nó cho phép chúng tôi thêm chức năng mới vào chức năng hiện có mà không sửa đổi nó.
Trong ví dụ bên dưới, bạn có thể thấy chức năng trang trí điển hình trông như thế nào trong Python:
def decorator(f):
def new_function():
print("Extra Functionality")
f()
return new_function
@decorator
def initial_function():
print("Initial Functionality")
initial_function()
Hãy phân tích chi tiết các yếu tố này:
- Đầu tiên chúng tôi tìm thấy chức năng trang trí
def decorator(f)
(các rắc ✨) có chức năngf
như một lý lẽ.
def decorator(f):
def new_function():
print("Extra Functionality")
f()
return new_function
- Chức năng trang trí này có chức năng lồng nhau,
new_function
. Nhận thấy như thế nàof
được gọi bên trongnew_function
để đạt được chức năng tương tự trong khi thêm chức năng mới trước khi gọi hàm (chúng ta cũng có thể thêm chức năng mới sau khi gọi hàm). - Hàm trang trí tự trả về hàm lồng nhau
new_function
. - Sau đó (bên dưới), chúng tôi tìm thấy chức năng sẽ là trang trí (kem ?)
initial_function
. Lưu ý cú pháp rất đặc biệt (@decorator
) phía trên tiêu đề chức năng.
@decorator
def initial_function():
print("Initial Functionality")
initial_function()
Nếu chúng ta chạy mã, chúng ta sẽ thấy đầu ra này:
Extra Functionality
Initial Functionality
Lưu ý cách hàm trang trí chạy ngay cả khi chúng ta chỉ gọi initial_function()
. Đây là điều kỳ diệu của việc thêm @decorator ?.
💡Lưu ý: Nói chung, chúng tôi sẽ viết @<decorator_function_name>
thay thế tên của chức năng trang trí sau biểu tượng @.
Tôi biết bạn có thể hỏi: điều này có liên quan như thế nào đến @property? @property là một công cụ trang trí tích hợp sẵn cho hàm property() trong Python. Nó được sử dụng để cung cấp chức năng “đặc biệt” cho các phương thức nhất định để làm cho chúng hoạt động như getters, setters hoặc deleters khi chúng ta định nghĩa các thuộc tính trong một lớp.
Bây giờ bạn đã quen thuộc với decorator, hãy xem một kịch bản thực tế về việc sử dụng @property!
🔸 Kịch bản trong thế giới thực: @property
Giả sử rằng lớp này là một phần của chương trình của bạn. Bạn đang làm mô hình một ngôi nhà với một House
lớp (tại thời điểm này, lớp chỉ có một giá bán thuộc tính thể hiện được xác định):
class House:
def __init__(self, price):
self.price = price
Thuộc tính thể hiện này là công khai vì tên của nó không có dấu gạch dưới ở đầu. Vì thuộc tính hiện đang ở chế độ công khai nên rất có khả năng bạn và các nhà phát triển khác trong nhóm của bạn đã truy cập và sửa đổi thuộc tính trực tiếp trong các phần khác của chương trình sử dụng ký hiệu dấu chấm, như thế này:
# Access value
obj.price
# Modify value
obj.price = 40000
💡 Mẹo: đối tượng đại diện cho một biến tham chiếu đến một thể hiện của House
.
Cho đến nay mọi thứ đang hoạt động tốt, phải không? Nhưng mà giả sử rằng bạn được yêu cầu đặt thuộc tính này được bảo vệ (không công khai) và xác thực giá trị mới trước khi gán nó. Cụ thể, bạn cần kiểm tra xem giá trị có phải là số float dương hay không. Bạn làm điều đó như thế nào? Hãy xem nào.
Thay đổi mã của bạn
Tại thời điểm này, nếu bạn quyết định thêm getters và setters, bạn và nhóm của bạn có thể sẽ hoảng sợ?. Điều này là do mỗi dòng mã truy cập hoặc sửa đổi giá trị của thuộc tính sẽ phải được sửa đổi để gọi getter hoặc setter tương ứng. Nếu không, mã sẽ bị hỏng ⚠️.
# Changed from obj.price
obj.get_price()
# Changed from obj.price = 40000
obj.set_price(40000)
Nhưng… Tài sản đến để giải cứu! Với @property
bạn và nhóm của bạn sẽ không cần sửa đổi bất kỳ dòng nào trong số đó vì bạn sẽ có thể thêm getters và setters “đằng sau hậu trường” mà không ảnh hưởng đến cú pháp mà bạn đã sử dụng để truy cập hoặc sửa đổi thuộc tính khi nó được công khai.
Tuyệt vời, phải không?
🔹 @property: Cú pháp và Logic
Nếu bạn quyết định sử dụng @property
lớp của bạn sẽ giống như ví dụ bên dưới:
class House:
def __init__(self, price):
self._price = price
@property
def price(self):
return self._price
@price.setter
def price(self, new_price):
if new_price > 0 and isinstance(new_price, float):
self._price = new_price
else:
print("Please enter a valid price")
@price.deleter
def price(self):
del self._price
Cụ thể, bạn có thể xác định ba phương pháp cho một tài sản:
- Một người bắt được – để truy cập giá trị của thuộc tính.
- Một người định cư – để đặt giá trị của thuộc tính.
- Một thợ xóa – để xóa thuộc tính thể hiện.
Giá bây giờ là “Được bảo vệ”
Xin lưu ý rằng giá bán thuộc tính hiện được coi là “được bảo vệ” vì chúng tôi đã thêm dấu gạch dưới ở đầu vào tên của nó trong self._price
:
self._price = price
Trong Python, theo quy ước, khi bạn thêm dấu gạch dưới ở đầu tên, bạn đang nói với các nhà phát triển khác rằng nó không nên được truy cập hoặc sửa đổi trực tiếp bên ngoài lớp. Nó chỉ nên được truy cập thông qua các trung gian (getters và setters) nếu chúng có sẵn.
🔸 Bắt
Ở đây chúng ta có phương thức getter:
@property
def price(self):
return self._price
Chú ý cú pháp:
@property
– Được sử dụng để chỉ ra rằng chúng ta sẽ xác định một thuộc tính. Lưu ý cách này ngay lập tức cải thiện khả năng đọc vì chúng ta có thể thấy rõ mục đích của phương pháp này.def price(self)
– Đầu đề. Lưu ý cách getter được đặt tên chính xác như thuộc tính mà chúng tôi đang xác định: giá bán. Đây là tên mà chúng ta sẽ sử dụng để truy cập và sửa đổi thuộc tính bên ngoài lớp. Phương thức này chỉ nhận một tham số hình thức, self, tham chiếu đến thể hiện.return self._price
– Dòng này chính xác là những gì bạn mong đợi ở một getter thông thường. Giá trị của thuộc tính được bảo vệ được trả về.
Đây là một ví dụ về việc sử dụng phương thức getter:
>>> house = House(50000.0) # Create instance
>>> house.price # Access value
50000.0
Lưu ý cách chúng tôi truy cập vào giá bán thuộc tính như thể nó là một thuộc tính công khai. Chúng tôi hoàn toàn không thay đổi cú pháp, nhưng chúng tôi thực sự đang sử dụng getter làm trung gian để tránh truy cập dữ liệu trực tiếp.
🔹 người định cư
Bây giờ chúng ta có phương thức setter:
@price.setter
def price(self, new_price):
if new_price > 0 and isinstance(new_price, float):
self._price = new_price
else:
print("Please enter a valid price")
Chú ý cú pháp:
@price.setter
– Dùng để chỉ đây là người định cư phương pháp cho giá bán tài sản. Lưu ý rằng chúng ta không phải sử dụng @tài sản.setterchúng tôi đang sử dụng @giá bán.setter. Tên của tài sản được bao gồm trước .setter.def price(self, new_price):
– Tiêu đề và danh sách các tham số. Lưu ý cách tên của thuộc tính được sử dụng làm tên của trình thiết lập. Chúng tôi cũng có một tham số chính thức thứ hai (giá mới), là giá trị mới sẽ được gán cho giá bán thuộc tính (nếu nó hợp lệ).- Cuối cùng, chúng ta có phần thân của setter nơi chúng ta xác thực đối số để kiểm tra xem nó có phải là số float dương hay không và sau đó, nếu đối số hợp lệ, chúng tôi cập nhật giá trị của thuộc tính. Nếu giá trị không hợp lệ, một thông báo mô tả sẽ được in. Bạn có thể chọn cách xử lý các giá trị không hợp lệ tùy theo nhu cầu của chương trình.
Đây là một ví dụ về việc sử dụng phương thức setter với @property:
>>> house = House(50000.0) # Create instance
>>> house.price = 45000.0 # Update value
>>> house.price # Access value
45000.0
Lưu ý cách chúng tôi không thay đổi cú pháp, nhưng bây giờ chúng tôi đang sử dụng một trung gian (bộ thiết lập) để xác thực đối số trước khi gán nó. Giá trị mới (45000.0) được truyền dưới dạng đối số cho setter :
house.price = 45000.0
Nếu chúng tôi cố gán một giá trị không hợp lệ, chúng tôi sẽ thấy thông báo mô tả. Chúng tôi cũng có thể kiểm tra xem giá trị chưa được cập nhật:
>>> house = House(50000.0)
>>> house.price = -50
Please enter a valid price
>>> house.price
50000.0
💡 Mẹo: Điều này chứng tỏ rằng phương thức setter đang hoạt động như một trung gian. Nó được gọi là “hậu trường” khi chúng tôi cố gắng cập nhật giá trị, vì vậy thông báo mô tả được hiển thị khi giá trị không hợp lệ.
🔸 Trình xóa
Cuối cùng, chúng ta có phương thức deleter:
@price.deleter
def price(self):
del self._price
Chú ý cú pháp:
@price.deleter
– Dùng để chỉ đây là thợ xóa phương pháp cho giá bán tài sản. Lưu ý rằng dòng này rất giống với @price.setter, nhưng bây giờ chúng tôi đang xác định phương thức xóa, vì vậy chúng tôi viết @price.thợ xóa.def price(self):
– Đầu đề. Phương thức này chỉ có một tham số chính thức được xác định, self.del self._price
– Phần thân, nơi chúng ta xóa thuộc tính thể hiện.
💡 Mẹo: Lưu ý rằng tên của thuộc tính được “tái sử dụng” cho cả ba phương thức.
Đây là một ví dụ về việc sử dụng phương thức deleter với @property:
# Create instance
>>> house = House(50000.0)
# The instance attribute exists
>>> house.price
50000.0
# Delete the instance attribute
>>> del house.price
# The instance attribute doesn't exist
>>> house.price
Traceback (most recent call last):
File "<pyshell#35>", line 1, in <module>
house.price
File "<pyshell#20>", line 8, in price
return self._price
AttributeError: 'House' object has no attribute '_price'
Thuộc tính thể hiện đã được xóa thành công?. Khi chúng tôi cố gắng truy cập lại, sẽ xảy ra lỗi do thuộc tính không còn tồn tại nữa.
🔹 Một số Mẹo cuối cùng
Bạn không nhất thiết phải xác định cả ba phương thức cho mọi thuộc tính. Bạn có thể xác định các thuộc tính chỉ đọc bằng cách chỉ bao gồm một phương thức getter. Bạn cũng có thể chọn xác định một getter và setter mà không cần một deleter.
Nếu bạn nghĩ rằng một thuộc tính chỉ nên được đặt khi thể hiện được tạo hoặc nó chỉ nên được sửa đổi bên trong lớp, bạn có thể bỏ qua trình thiết lập.
Bạn có thể chọn phương pháp nào để đưa vào tùy thuộc vào ngữ cảnh mà bạn đang làm việc.
🔸 Tóm lại
- Bạn có thể xác định các thuộc tính bằng cú pháp @property, cú pháp này nhỏ gọn và dễ đọc hơn.
- @property có thể được coi là cách “pythonic” để xác định getters, setters và deleters.
- Bằng cách xác định các thuộc tính, bạn có thể thay đổi cách triển khai bên trong của một lớp mà không ảnh hưởng đến chương trình, do đó, bạn có thể thêm các trình thu thập, trình thiết lập và trình xóa đóng vai trò trung gian “ở hậu trường” để tránh truy cập hoặc sửa đổi dữ liệu trực tiếp.
Tôi thực sự hy vọng bạn thích bài viết của tôi và thấy nó hữu ích. Để tìm hiểu thêm về Thuộc tính và Lập trình hướng đối tượng trong Python, hãy xem khóa học trực tuyến của tôi, bao gồm hơn 6 giờ bài giảng video, bài tập mã hóa và các dự án nhỏ.