4. 描述符协议
什么是描述符协议
描述符协议(Descriptor Protocol)是Python中一种底层机制,它允许对对象的属性的取值、设置值和删除属性操作行为进行控制。从而实现更深层次的封装。
实现了描述符协议的类就是描述符。
描述符协议包含 __get__
、__set__
和 __delete__
三个核心方法,一个类只要实现了其中任意一个方法,就被视为描述符。
Python 中的 property、classmethod、staticmethod 都是描述符。
描述符协议的核心方法
描述符协议包含三个核心方法:
def __get__(self, instance, owner):
- 用于控制属性的访问(获取)操作。
def __set__(self, instance, value):
- 用于控制属性的赋值操作。
def __delete__(self, instance):
- 用于控制属性的删除操作。
参数:
- instance是拥有该描述符的对象(如果是类访问则为None)。
- owner是拥有该描述符的类。
- value是要设置的值。
示例
第一步: 定义一个 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 的取值和赋值进行了控制。