Giới Thiệu về Python Descriptor
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, decorators.
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
1 2 3 4 5 6 |
|
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
1 2 3 4 5 6 7 8 9 10 |
|
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
1 2 |
|
May mắn thay, ta có thể sử dụng property
để giải quyết vấn đề này
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
1 2 3 4 5 6 |
|
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 và @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 salary
và rating
. Chúng ta có thể làm tương tự như sau
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
1 2 3 |
|
NonNegativeDescriptor là một descriptor vì class này cài đặt 2 phương thức __get__
và __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ốinstance
vàowner
.instance
là instance của class mà Descriptor được bind tới.owner
là class củainstance
. Trong trường hợp, không cóinstance
nào được gọi,owner
sẽ là None.__set__
: Nhận 2 tham sốinstance
vàvalue
.instance
có ý nghĩa như trong__get__
, value là giá trị muốn set cho thuộc tính củainstance
__delete__
: Nhận 1 tham sốinstance
Trong class Programmer
, chúng ta tạo ra 3 Descriptor ở mức class là age
, salary
và rating
. 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.
4. Descriptor và Metaclass
Một điểm cần lưu ý đó là trong descriptor, có sử dụng biến label để bind giữa descriptor và thuộc tính của class. Ta có thể sử dụng Metaclass để giải quyết vấn đề này
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Metaclass hoạt động như thế nào, sẽ được giới thiệu trong bài viết tiếp theo.
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)