Giới thiệu Python Descriptor

Quản lý truy cập đến thuộc tính thông qua các Python Descriptors

Tìm hiểu cách dễ dàng tạo và áp dụng các Python descriptors.

Alex Starostin, Kỹ sư bảo đảm chất lượng, IBM

Ảnh của Alex StarostinAlex Starostin là một kỹ sư đảm bảo chất lượng ở phòng thí nghiệm IBM Australia Development. Công việc trọng tâm của ông là kiểm thử phần mềm và đảm bảo chất lượng cho đội ngũ phát triển trong môi trường phương pháp Agile. Bên cạnh việc phải có trách nhiệm thường xuyên, Alex thích dành trọn thời gian còn lại của mình để học những công nghệ và ngôn ngữ lập trình mới.



08 03 2013

Giới thiệu

Các Python descriptors đã được giới thiệu trong Python 2.2, cùng với các lớp kiểu dáng mới, nhưng chúng vẫn chưa được sử dụng rộng rãi. Các Python descriptors là cách để tạo ra các thuộc tính được quản lý. Trong số rất nhiều lợi thế của chúng, các thuộc tính được quản lý đó được sử dụng để bảo vệ một thuộc tính khỏi những thay đổi hoặc để tự động cập nhật các giá trị của một thuộc tính phụ thuộc.

Các descriptors làm tăng sự hiểu biết về Python và cải thiện các kỹ năng mã hóa. Bài này giới thiệu giao thức descriptors và trình bày cách tạo và sử dụng các descriptors.

Giao thức descriptors

Giao thức descriptors trong Python chỉ đơn giản là một cách để xác định những gì sẽ xảy ra khi tham chiếu một thuộc tính trong một mô hình. Nó cho phép một lập trình viên quản lý truy cập thuộc tính dễ dàng và hiệu quả:

  • set
  • get
  • delete

Trong các ngôn ngữ lập trình khác, các descriptors được gọi là setter và getter, ở đây các hàm public được sử dụng để Get (nhận giá trị) và Set (đặt giá trị ) cho một biến private. Python không có một khái niệm về các biến private và có thể coi giao thức descriptors như là một cách của Python để đạt được điều tương tự.

Nhìn chung, một descriptor là một thuộc tính của đối tượng với một hành vi kết buộc, một hành vi mà việc truy cập thuộc tính của nó bị ghi đè bằng các phương thức trong giao thức descriptors. Các phương thức đó là __get__, __set____delete__. Nếu định nghĩa một phương thức bất kỳ trong số này cho một đối tượng, người ta nói rằng đó là một descriptor. Hãy xem xét kỹ hơn các phương thức này trong Liệt kê 1.

Liệt kê 1. Các phương thức descriptors
__get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance)

Ở đây:

__get__ truy cập thuộc tính. Nó trả về giá trị của thuộc tính hoặc đưa ra ngoại lệ AttributeError nếu không tồn tại thuộc tính được yêu cầu.

__set__ được gọi là một phép gán thuộc tính. Không trả về cái gì cả.

__delete__ kiểm soát hoạt động xóa. Không trả về cái gì cả.

Điều quan trọng cần lưu ý là các descriptors được gán cho một lớp, chứ không gán cho một cá thể của lớp. Việc sửa đổi lớp này sẽ ghi đè lên hoặc xóa chính descriptor đó, chứ không phải kích hoạt mã của nó.


Khi nào cần các descriptors

Hãy xem xét một thuộc tính email. Việc xác minh định dạng email đúng là cần thiết trước khi gán một giá trị cho thuộc tính đó. Descriptor này cho phép xử lý địa chỉ email thông qua một biểu thức chính quy và định dạng của nó được xác nhận hợp lệ trước khi gán nó cho một thuộc tính.

Trong nhiều trường hợp khác, các giao thức descriptors trong Python kiểm soát truy cập đến các thuộc tính, chẳng hạn như bảo vệ thuộc tính name.


Tạo các descriptors

Bạn có thể tạo ra một descriptor theo một số cách sau:

  • Tạo một lớp và ghi đè lên bất kỳ các phương thức nào của descriptor: __set__, __ get____delete__. Phương thức này được sử dụng khi cần descriptor giống nhau qua nhiều lớp và các thuộc tính khác nhau, ví dụ, để xác nhận hợp lệ cho kiểu.
  • Sử dụng một kiểu property (đặc tính) là một cách đơn giản hơn và linh hoạt hơn để tạo ra một descriptor.
  • Sử dụng sức mạnh của các bộ trang trí đặc tính, là một tổ hợp của phương thức về kiểu đặc tính và các bộ trang trí của Python.

Tất cả các ví dụ dưới đây là giống nhau theo quan điểm hoạt động. Sự khác biệt nằm ở việc thực hiện.


Tạo các descriptors bằng cách sử dụng các phương thức lớp

Liệt kê 2 cho thấy tính đơn giản của việc kiểm soát gán thuộc tính trong Python.

Liệt kê 2. Tạo các descriptors bằng cách sử dụng các phương thức lớp
class Descriptor(object):

    def __init__(self):
        self._name = ''

    def __get__(self, instance, owner):
        print "Getting: %s" % self._name
        return self._name

    def __set__(self, instance, name):
        print "Setting: %s" % name
        self._name = name.title()

    def __delete__(self, instance):
        print "Deleting: %s" %self._name
        del self._name

class Person(object):
    name = Descriptor()

Hãy sử dụng mã này và xem kết quả ban đầu:

>>> user = Person()
>>> user.name = 'john smith'
Setting: john smith
>>> user.name
Getting: John Smith
'John Smith'
>>> del user.name
Deleting: John Smith

Một lớp descriptor được tạo ra bằng cách ghi đè các phương thức __set__(), __get__()__delete__() của lớp cha mẹ sao cho

  • get sẽ in ra Getting
  • delete sẽ in ra Deleting
  • set sẽ in ra Setting

và thay đổi giá trị thuộc tính thành tiêu đề (viết hoa chữ cái đầu tiên, viết thường các chữ khác) trước khi gán. Việc này rất tiện dụng, chẳng hạn khi lưu và in các tên.

Việc chuyển đổi thành chữ hoa cũng có thể được chuyển sang cho phương thức __get__(). _value sẽ có giá trị ban đầu và sẽ được chuyển đổi thành tiêu đề khi có yêu cầu get.


Tạo các descriptors bằng cách sử dụng kiểu property

Trong khi bộ mô tả được xác định trong Liệt kê 2 là hợp lệ và hoạt động được, một phương thức khác là thông qua kiểu đặc tính. Với hàm property(), dễ dàng tạo ra một bộ mô tả có thể sử dụng cho bất kỳ thuộc tính nào. Cú pháp để tạo property()property(fget=None, fset=None, fdel=None, doc=None) ở đây:

  • fget – phương thức get (nhận) thuộc tính
  • fset – phương thức set (thiết lập) thuộc tính
  • fdel – phương thức delete (xóa) thuộc tính
  • doc – docstring (chuỗi ký tự tài liệu)

Viết lại ví dụ bằng cách sử dụng property, như trongLiệt kê 3.

Liệt kê 3. Tạo descriptor bằng kiểu property
class Person(object):
    def __init__(self):
        self._name = ''

    def fget(self):
        print "Getting: %s" % self._name
        return self._name
    
    def fset(self, value):
        print "Setting: %s" % value
        self._name = value.title()

    def fdel(self):
        print "Deleting: %s" %self._name
        del self._name
    name = property(fget, fset, fdel, "I'm the property.")

Hãy sử dụng mã này và xem kết quả:

>>> user = Person()
>>> user.name = 'john smith'
Setting: john smith
>>> user.name
Getting: John Smith
'John Smith'
>>> del user.name
Deleting: John Smith

Rõ ràng, kết quả như nhau. Lưu ý ở đây là các phương thức fget, fsetfdel đều là tùy chọn, nhưng nếu một phương thức không được xác định rõ, sẽ xảy ra một ngoại lệ AttributeError khi cố thực hiện hoạt động tương ứng. Ví dụ, đặc tính name được khai báo là None như với fset và sau đó nhà phát triển cố gắng gán giá trị cho thuộc tính name thì một ngoại lệ AttributeError được đưa ra.

Có thể sử dụng điều này để định nghĩa thuộc tính chỉ đọc trong hệ thống.

name = property(fget, None, fdel, "I'm the property")
user.name = 'john smith'

Kết quả:

Traceback (most recent call last):
File stdin, line 21, in mоdule
user.name = 'john smith'
AttributeError: can't set attribute

Tạo các descriptors bằng cách sử dụng các bộ trang trí đặc tính

Các bộ mô tả có thể được tạo ra bằng các bộ trang trí của Python, như trong Liệt kê 4. Bộ trang trí Python là một sự thay đổi đặc trưng cho cú pháp Python, cho phép sửa lại thuận tiện hơn các hàm và các phương thức. Trong trường hợp này, các phương thức quản lý thuộc tính bị sửa đổi. Hãy tìm thêm thông tin về việc áp dụng các bộ trang trí Python trong bài viết, Các bộ trang trí làm mọi thứ trở nên dễ dàng hơn trên developerWorks.

Liệt kê 4. Tạo các descriptors với các bộ trang trí đặc tính
class Person(object):

    def __init__(self):
        self._name = ''

    @property
    def name(self):
        print "Getting: %s" % self._name
        return self._name

    @name.setter
    def name(self, value):
        print "Setting: %s" % value
        self._name = value.title()

    @name.deleter
    def name(self):
        print ">Deleting: %s" % self._name
        del self._name

Tạo các descriptors tại thời điểm chạy

Tất cả các ví dụ trước hoạt động với thuộc tính name. Hạn chế của cách tiếp cận này là sự cần thiết phải ghi đè __set__(), __get__()__delete__() riêng biệt cho từng thuộc tính. Liệt kê 5 cung cấp một giải pháp khả thi khi một nhà phát triển muốn thêm các thuộc tính đặc tính tại thời gian chạy. Liệt kê này sử dụng kiểu đặc tính để xây dựng một bộ mô tả dữ liệu (data descriptor).

Liệt kê 5. Tạo các descriptors tại thời gian chạy
class Person(object):

    def addProperty(self, attribute):
        # create local setter and getter with a particular attribute name 
        getter = lambda self: self._getProperty(attribute)
        setter = lambda self, value: self._setProperty(attribute, value)

        # construct property attribute and add it to the class
        setattr(self.__class__, attribute, property(fget=getter, \
                                                    fset=setter, \
                                                    doc="Auto-generated method"))

    def _setProperty(self, attribute, value):
        print "Setting: %s = %s" %(attribute, value)
        setattr(self, '_' + attribute, value.title())    

    def _getProperty(self, attribute):
        print "Getting: %s" %attribute
        return getattr(self, '_' + attribute)

Hãy chạy thử mã này:

>>> user = Person()
>>> user.addProperty('name')
>>> user.addProperty('phone')
>>> user.name = 'john smith'
Setting: name = john smith
>>> user.phone = '12345'
Setting: phone = 12345
>>> user.name
Getting: name
'John Smith'
>>> user.__dict__
{'_phone': '12345', '_name': 'John Smith'}

Mã này tạo ra các thuộc tính namephone tại thời gian chạy. Có thể truy cập tới chúng bằng tên tương ứng, nhưng chúng được lưu trữ trong từ điển namespace đối tượng là _name và _phone, như được quy định trong phương thức _setProperty. Về cơ bản, namephone là các hàm truy cập vào các thuộc tính nội bộ _name và _phone.

Bạn có thể có một câu hỏi liên quan đến một thuộc tính _name trong hệ thống khi nhà phát triển cố gắng thêm thuộc tính đặc tính name. Câu trả lời là nó sẽ ghi đè lên thuộc tính _name hiện có bằng thuộc tính đặc tính mới. Mã này cho phép kiểm soát các thuộc tính được xử lý bên trong một lớp ra sao.


Kết luận

Các descriptors trong Python cho phép quản lý thuộc tính mạnh mẽ và linh hoạt với các lớp kiểu dáng mới. Được kết hợp với các bộ trang trí, chúng làm cho việc lập trình dễ dàng, cho phép tạo ra các Settercác Getter, cũng như các thuộc tính chỉ đọc (read-only). Nó cũng cho phép bạn chạy xác nhận hợp lệ thuộc tính theo yêu cầu theo giá trị hoặc kiểu. Bạn có thể áp dụng các descriptors trong nhiều lĩnh vực, nhưng hãy sử dụng chúng hết sức thận trọng để tránh làm phức tạp mã không cần thiết xuất phát từ việc ghi đè lên các hành vi bình thường của một đối tượng.

Tài nguyên

Học tập

Lấy sản phẩm và công nghệ

  • Đánh giá các sản phẩm của IBM theo cách phù hợp với bạn nhất: Tải về một bản dùng thử sản phẩm, hãy dùng thử một sản phẩm trực tuyến, sử dụng một sản phẩm trong một môi trường đám mây hoặc dành một vài giờ trong SOA Sandbox để học cách thực hiện kiến trúc hướng dịch vụ (SOA) một cách hiệu quả.

Thảo luận

Bình luận

developerWorks: Đăng nhập

Các trường được đánh dấu hoa thị là bắt buộc (*).


Bạn cần một ID của IBM?
Bạn quên định danh?


Bạn quên mật khẩu?
Đổi mật khẩu

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Ở lần bạn đăng nhập đầu tiên vào trang developerWorks, một hồ sơ cá nhân của bạn được tạo ra. Thông tin trong bản hồ sơ này (tên bạn, nước/vùng lãnh thổ, và tên cơ quan) sẽ được trưng ra cho mọi người và sẽ đi cùng các nội dung mà bạn đăng, trừ khi bạn chọn việc ẩn tên cơ quan của bạn. Bạn có thể cập nhật tài khoản trên trang IBM bất cứ khi nào.

Thông tin gửi đi được đảm bảo an toàn.

Chọn tên hiển thị của bạn



Lần đầu tiên bạn đăng nhập vào trang developerWorks, một bản trích ngang được tạo ra cho bạn, bạn cần phải chọn một tên để hiển thị. Tên hiển thị của bạn sẽ đi kèm theo các nội dung mà bạn đăng tải trên developerWorks.

Tên hiển thị cần có từ 3 đến 30 ký tự. Tên xuất hiện của bạn phải là duy nhất trên trang Cộng đồng developerWorks và vì lí do an ninh nó không phải là địa chỉ email của bạn.

Các trường được đánh dấu hoa thị là bắt buộc (*).

(Tên hiển thị cần có từ 3 đến 30 ký tự)

Bằng việc nhấn Gửi, bạn đã đồng ý với các điều khoản sử dụng developerWorks Điều khoản sử dụng.

 


Thông tin gửi đi được đảm bảo an toàn.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=70
Zone=Nguồn mở
ArticleID=860893
ArticleTitle=Giới thiệu Python Descriptor
publish-date=03082013