Thuộc tính (@property) trong Python
Một số ngôn ngữ hướng đối tượng như Java hỗ trợ các thuộc tính đối tượng riêng(private); mà không thể truy cập trực tiếp từ bên ngoài.
Các lập trình viên thường phải viết các phương thức getter và setter để truy cập các thuộc tính private như vậy.
Tuy nhiên trong Python, tất cả các thuộc tính và phương thức đều công khai, vì vậy các phương thức getters, setters không còn ý nghĩa. Nếu bạn muốn ngăn truy cập trực tiếp vào một thuộc tính, bạn nên xác định nó như một thuộc tính. Đó là một cách đơn giản để tùy chỉnh quyền truy cập vào một thuộc tính.
Nội dung của bài
Định nghĩa một thuộc tính
Hãy cùng xem ví dụ dưới đây, chúng ta sẽ định nghĩa một lớp Pet. Class này có thuộc tính là hidden_name, đây là thuộc tính mà chúng ta không muốn các lớp khác truy cập trực tiêp. Do đó, chúng ta cài đặt hai phươn thức: một getter có tên là get_name(), một setter có tên là set_name().
class Pet():
def __init__(self, value):
self.hidden_name = value
# getter function
def get_name(self):
print('Getting name:')
return self.hidden_name
# setter function
def set_name(self, value):
print('Setting name to', value)
self.hidden_name = value
# make a property
name = property(get_name, set_name)
Lớp Pet giống với lớp Animal mà chúng ta đã học ở bài lớp và đối tượng trong Python. Có phần khác biệt là đoạn khai báo: name = property(get_name, set_name)
Khi đọc câu lệnh này, Python sẽ tạo ra một thuộc tính mới của lớp có tên là name và định nghĩa hai phương thức get_name() và set_name() là thuộc tính.
Lúc này, khi một đối tượng là thể hiện của lớp Pet gọi đến thuộc tính name thì thực tế nó đang gọi đến phương thức get_name()
my_pet = Pet("Xuka")
print(my_pet.name)
# prints Getting name:
# Xuka
Khi chúng ta thây đổi giá trị của thuộc tính name thì hàm set_name() được gọi:
p.name = "Shey"
print(p.name)
# Prints Getting name:
# Shey
Sử dụng Decorator @property
Có một cách khác để khai báo một thuộc tính là sử dụng @property. Hãy viêt lại lớp Pet trên bằng việc khai báo ba phương thức cùng có tên là name nhưng sử dụng các decorator trước các phương thức:
- @property đứng trước phương thức getter.
- @name.setter đứng trước phương thức setter.
- @name.deleter đứng trước phương thức deleter.
class Pet():
def __init__(self, value):
self.hidden_name = value
@property
def name(self):
print('Getting name:')
return self.hidden_name
@name.setter
def name(self, value):
print('Setting name to', value)
self.hidden_name = value
@name.deleter
def name(self):
print('Deleting name')
del self.hidden_name
Ở đây, phương thức đầu tiên là getter và thiết lập phương thức name như một thuộc tính. Hai phương thức khác đính kèm setter và deleter vào thuộc tính name. Bạn vẫn có thể truy cập tên như thể nó là một thuộc tính:
p = Pet('Scooby Doo')
# calls the getter
print(p.name)
# Prints Getting name:
# Scooby Doo
# calls the setter
p.name = 'Xuka'
# calls the deleter
del p.name
# Prints Deleting name
Ví dụ thực tế
Các thuộc tính thường được sử dụng trong các trường hợp bạn muốn thêm xử lý bổ sung (ví dụ: kiểm tra kiểu hoặc xác thực tính đúng đắn của dữ liệu) để nhận cập nhật hoặc truy xuất giá trị của thuộc tính.
Ví dụ: mã bên dưới xác định một thuộc tính bổ sung kiểm tra kiểu đơn giản cho một thuộc tính:
class Pet:
def __init__(self, value):
self.name = value
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._name = value
@name.deleter
def name(self):
raise AttributeError("Can't delete attribute")
my_pet = Pet(100) # Triggers TypeError: Expected a string
my_pet = Pet('Scooby Doo')
print(my_pet.name) # Prints Scooby Doo
my_pet.name = 100 # Triggers TypeError: Expected a string
del my_pet.name # Triggers AttributeError: Can't delete attribute
Trong ví dụ trên, thuộc tính name có kiểu là một chuỗi, khi chúng ta khởi tạo, hoặc gán giá trị cho thuộc tính name thì sẽ trả ra ngoại lệ TypeError.
Sử dụng thuộc tính như là computed attributes
Ví dụ:
class Rectangle(object):
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
r = Rectangle(10, 5)
print(r.area)
# prints 50
Chúng ta xây dựng tính thuộc tính area, trả ra giá trị là diện thích của hình chữ nhật. Việc sử dụng thuộc tính làm computed attribute rất hợp lý.
Mở rộng thuộc tính trong lớp con
Bởi vì một thuộc tính không phải là một phương thức mà là tập hợp ba phương thức getter, setter, deleter. Bạn cần xác định rõ cần định nghĩa lại tất cả các phương thức hay một phương thức nhất định để tránh nhầm lẫn.
class Pet():
def __init__(self, value):
self.hidden_name = value
@property
def name(self):
print('Getting name:')
return self.hidden_name
@name.setter
def name(self, value):
print('Setting name to', value)
self.hidden_name = value
@name.deleter
def name(self):
print('Deleting name')
del self.hidden_name
class SubPet(Pet):
@property
def name(self):
print('Inside subpet getter')
return super().name
@name.setter
def name(self, value):
print('Inside subpet setter')
super(SubPet, SubPet).name.__set__(self, value)
@name.deleter
def name(self):
print('Inside subpet deleter')
super(SubPet, SubPet).name.__delete__(self)
Ví dụ trên chúng ta cài đặt lớp con SubPet kết thừa từ lớp Pet kế thừa thuộc tính name của phương thức cha.
Cùng xem cách sử dụng lớp SubPet
subpet = SubPet("Xuka")
print(subpet.name)
# Prints Inside subpet getter
# Prints Getting name:
# Xuka
Ở ví dụ trên chúng ta viết lại tất cả các phương thức của lớp cha. Nếu chỉ muốn định nghĩa lại một phương thức chúng ta không thể sử dụng decorator @property mà phải dùng như sau:
class SubPet(Pet):
@Pet.name.getter
def name(self):
print('Inside subpet getter')
return super().name
Tổng kết
Chúng ta đã tìm hiểu về thuộc tính và cách dùng trong một số trường hợp như dùng làm computed attribute. Kiến thức này có thể hữu ích cho các bạn trong quá trình phát triển các ứng dụng.