Hướng dẫn python getter decorator - người trang trí python getter

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

  • 1 Định nghĩa một thuộc tính Định nghĩa một thuộc tính
  • 2 Sử dụng Decorator @property Sử dụng Decorator @property
  • 3 Ví dụ thực tế Ví dụ thực tế
    • 3.1 Sử dụng thuộc tính như là computed attributes Sử dụng thuộc tính như là computed attributes
  • 4 Mở rộng thuộc tính trong lớp con Mở rộng thuộc tính trong lớp con
  • 5 Tổng kết Tổng kết

Đị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 attributescomputed 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.