4. 描述符协议

什么是描述符协议

描述符协议(Descriptor Protocol)是Python中一种底层机制,它允许对对象的属性的取值、设置值和删除属性操作行为进行控制。从而实现更深层次的封装。

实现了描述符协议的类就是描述符。

描述符协议包含 __get____set____delete__ 三个核心方法,一个类只要实现了其中任意一个方法,就被视为描述符。

Python 中的 property、classmethod、staticmethod 都是描述符。

描述符协议的核心方法

描述符协议包含三个核心方法:

  1. def __get__(self, instance, owner):
    • 用于控制属性的访问(获取)操作。
  2. def __set__(self, instance, value):
    • 用于控制属性的赋值操作。
  3. def __delete__(self, instance):
    • 用于控制属性的删除操作。

参数:

示例

第一步: 定义一个 AreaDescriptor 类,实现了描述符协议的三个方法,但是里面只有一句打印 print。

# 描述符协议 示例:
# https://weimingze.com

import math

class AreaDescriptor:
    def __get__(self, instance, owner):
        print('__get__(', instance, owner, ')')

    def __set__(self, instance, value):
        print('__set__(', instance, value, ')')

    def __delete__(self, instance):
        print('__delete__(', instance, ')')

class Circle:  # 圆类
    def __init__(self, r=1):
        self.radius = r  # 半径

    area = AreaDescriptor()

c = Circle(10)
a = c.area
print('a:', a)
c.area = 200
print(c.radius)
del c.area

上述程序对 c.area 取值则调用 AreaDescriptor.__get__ 方法,赋值到用 AreaDescriptor.__set__ 方法,删除调用 AreaDescriptor.__delete__ 方法。

改进上述程序,定义 AreaDescriptor 的 初始化方法 def __init__(self, fget=None, fset=None, fdel=None, doc=None):,使用像 property 特性属性的构造函数一样,传入相应的取值、设置值和删除的函数。并用内部属性绑定。

# 描述符协议 示例:
# https://weimingze.com

import math

class AreaDescriptor:
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.__getter_cb = fget
        self.__setter_cb = fset
        self.__delete_cb = fdel

    def __get__(self, instance, owner):
        print('__get__(', instance, owner, ')')
        if self.__getter_cb is None:
            raise AttributeError('此属性不可以取值!')
        return self.__getter_cb(instance)

    def __set__(self, instance, value):
        print('__set__(', instance, value, ')')
        if self.__setter_cb is None:
            raise AttributeError('此属性不允许设置值!')
        self.__setter_cb(instance, value)

    def __delete__(self, instance):
        print('__delete__(', instance, ')')

class Circle:  # 圆类
    def __init__(self, r=1):
        self.radius = r  # 半径

    def get_area(self):
        return math.pi * self.radius ** 2

    def set_area(self, area):
        self.radius = math.sqrt(area / math.pi)

    area = AreaDescriptor(get_area, set_area)

c = Circle(10)
a = c.area
print('a:', a)
c.area = 200
print(c.radius)
del c.area

上述程序完善了 AreaDescriptor 的 __get__ 并调用 Circel 类中的 get_area 实现了取值。同样完善了 AreaDescriptor的 __set__方法 并调用 Circel 类中的 set_area 实现了赋值操作。

这个 AreaDescriptor 就是一个描述符,他实现了对Circle 对象的属性 area 的取值和赋值进行了控制。