Giới thiệu về python descriptor
Python
43
White

kiennt viết ngày 27/05/2015

Trong các bài viết trước, chúng tôi đã giới thiệu về các kiến thức cơ bản trong python, như object trong python, lazy property.

Bài viết này sẽ giới thiệu một kỹ nâng cao trong Python, đó là descriptor

1. Ví dụ về descriptor

Xét ví dụ khi chúng ta muốn xây dựng mô hình cho bài toán về các lập trình viên

class Programmer(object):
    def __init__(self, name, age, salary, rating):
        self.name = name
        self.age = age
        self.salary = salary
        self.rating = rating

Giờ nếu bạn muốn thêm một điều kiện là tuổi của lập trình viên phải luôn lớn hơn 0, bạn có thể cài đặt như sau

class Programmer(object):
    def __init__(self, name, age, salary, rating):
        self.name = name
        self.salary = salary
        self.rating = rating

        if age > 0:
            self.age = age
        else:
            raise ValueError("Negative value not allowed: %s" % age)

Tuy nhiên với cách làm này, bạn vẫn có thể làm cho age < 0, nếu gán giá trị của age trực tiếp từ instance của Programmer

>>> kiennt = Programmer('kiennt', 26, 500, 5)
>>> kiennt.age = -10

May mắn thay, ta có thể sử dụng property để giải quyết vấn đề này

class Programmer(object):
    def __init__(self, name, age, salary, rating):
        self._age = None # tạo một thuộc tính private cho age

        self.name = name
        self.age = age
        self.salary = salary
        self.rating = rating

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if age > 0:
            self._age = value
        else:
            raise ValueError("Negative value not allowed: %s" % age)
>>> kiennt = Programmer('kiennt', 26, 500, 5)
>>> try:
        kiennt.age = -10
    except ValueError:
        print "Cannot set negative value"
Cannot set negative value

Cách chúng ta làm ở đây đó là tạo ra một biến private _age để chứa giá trị thật của age. Và sử dụng @getter@setter để bind thuộc tính age với 2 method. Trong 2 method này, chúng ta sẽ cài đặt logic cho việc gán trị của age. Khi chúng ta gọi kiennt.age = value, python sẽ tự động gọi đến setter của age, còn nếu chỉ gọi kiennt.age (không có gán giá trị), thì getter sẽ được gọi.

2. Vấn đề của getter và setter

Nếu giờ, chúng ta cũng muốn kiểm tra giá trị của hai thuộc tính salaryrating. Chúng ta có thể làm tương tự như sau

class Programmer(object):
    def __init__(self, name, age, salary, rating):
        self._age = None # tạo một thuộc tính private cho age
        self._salary = None # tạo một thuộc tính private cho salary
        self._rating = None # tạo một thuộc tính private cho rating

        self.name = name
        self.age = age
        self.salary = salary
        self.rating = rating

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if age > 0:
            self._age = value
        else:
            raise ValueError("Negative value not allowed: %s" % age)

    @property
    def salary(self):
        return self._salary

    @age.setter
    def salary(self, value):
        if salary > 0:
            self._salary = value
        else:
            raise ValueError("Negative value not allowed: %s" % age)

    @property
    def rating(self):
        return self._rating

    @age.setter
    def rating(self, value):
        if rating > 0:
            self._rating = value
        else:
            raise ValueError("Negative value not allowed: %s" % age)

Tuy nhiên cách làm này làm cho code của chúng ta có qúa nhiều đoạn code lặp về logic. Đây chính là lúc descriptor có thể sử dụng.

3. Descriptor

Descriptor cho phép chúng ta bind cách xử lý truy cập của một thuộc tính trong class A với một class B khác. Nói cách khác, nó cho phép đưa việc truy cập thuộc tính ra ngoài class. Sau đây là cách cài đặt đối với bài toán của chúng ta

class NonNegativeDescriptor(object):
    def __init__(self, label):
        self.label = label

    def __get__(self, instance, owner):
        return instance.__dict__.get(self.label)

    def __set__(self, instance, value):
        if value > 0:
            instance.__dict__[self.label] = value
        else:
            raise ValueError("Negative value not allowed: %s" % age)


class Programmer(object):
    age = NonNegativeDescriptor('age')
    salary = NonNegativeDescriptor('salary')
    rating = NonNegativeDescriptor('rating')

    def __init__(self, name, age, salary, rating):
        self.name = name
        self.age = age
        self.salary = salary
        self.rating = rating
>>> kiennt = Programmer('kiennt', 26, 500, 5)
>>> print kiennt.age
>>> kiennt.age = 20

NonNegativeDescriptor là một descriptor vì class này cài đặt 2 phương thức __get____set__. Python nhận ra một class là descriptor nếu như class đó implement một trong 3 phương thức.

  • __get__: Nhận 2 tham số instanceowner. instance là instance của class mà Descriptor được bind tới. owner là class của instance. Trong trường hợp, không có instance nào được gọi, owner sẽ là None.
  • __set__: Nhận 2 tham số instancevalue. instance có ý nghĩa như trong __get__, value là giá trị muốn set cho thuộc tính của instance
  • __delete__: Nhận 1 tham số instance

Trong class Programmer, chúng ta tạo ra 3 Descriptor ở mức class là age, salaryrating.
Khi gọi print kiennt.age, python sẽ nhận ra age là một descriptor, nên nó sẽ gọi đến hàm __get__ của descriptor NonNegativeDescriptor.__get__(kiennt, Programmer). Tương tự khi gán giá trị cho kiennt.age = 20, hàm __set__ của descriptor cũng được gọi NonNegativeDescriptor.__set__(kiennt, 20).

Nếu chúng ta gọi Programmer.age, thì hàm __get__ sẽ được gọi với owner = None.

Kết luận

Bài viết này giới thiệu với các bạn về descriptor trong Python. Với descriptor, chúng ta có thể chuyển việc can thiệp vào từng thuộc tính của một instance trong class tới việc can thiệp vào thuộc tính ở mức class. Cùng với metaclass, descriptor được sử dụng như một ma thuật đen (black magic) trong metaprogramming. Descriptor được sử dụng rất nhiều khi xây dựng các bộ thư viện về ORM (django ORM, peewee, redisco)

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

kiennt

30 bài viết.
288 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
93 18
Mọi chuyện bắt đầu từ nắm 2013 trong quá trình xây dựng chức năng login với Facebook, tôi đã tìm ra một cách để tấn công vào các hệ thống login với...
kiennt viết hơn 2 năm trước
93 18
White
64 4
Trong tuần vừa rồi, mình có đọc chương 7 cuốn sách (Link). Bài viết này nhằm mục đích giúp mình tổng hợp lại những kiến thức đã học được về chương ...
kiennt viết 6 tháng trước
64 4
White
29 5
1. Đặt vấn đề Một trong các vấn đề của một hệ thống backend là bài toán điều phối request tới các nguồn dữ liệu. Xét bài toán với một hệ thống bl...
kiennt viết 2 năm trước
29 5
Bài viết liên quan
White
1 0
Mở đầu Như đã nói ở bài trước, mình đang nghiên cứu về Spark nên cần log lại một số thứ để dành sau này dùng đến :smile: Đối tượng hướng đến vẫn ...
Phạm Quốc Thắng viết hơn 2 năm trước
1 0
White
5 3
Observer pattern (python example) 1. Observer là gì : Theo như (Link) Observer Pattern là : A software design pattern in which an object, calle...
Khôi Trọng Nguyễn viết 2 năm trước
5 3
White
0 0
Web Framework Flask định nghĩa route bằng annotations kiểu như @route('/users/add', methods='GET']) def user_add(): pass Lợi thế của cách là...
studybot viết 3 năm trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
30 bài viết.
288 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!