Loading

一文掌握 Python 的描述符协议

描述符介绍

描述符本质就是一个新式类,在这个新式类中,至少要实现了__get__()__set__()__delete__()中的一个。这也被称为描述符协议。

class Myclass(object): 
    
    def __get__(self, instance, owner):
        '''调用一个属性时触发'''
        pass
    
    def __set__(self, instance, value):
        '''为一个属性赋值时触发'''
        pass
    
    def __delete__(self, instance):
        '''使用del删除属性时触发'''
        pass

描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

数据描述符

至少实现了__get__()__set__()

class Myclass(object):
    
    def __set__(self, instance, value):
        print('set')
    
    def __get__(self, instance, owner):
        print('get')

非数据描述符

没有实现__set__()

class Myclass(object):

    def __get__(self, instance, owner):
        print('get')

注1:必须把描述符定义成这个类的类属性,不能为定义到构造函数中
注2:要严格遵循该优先级,优先级由高到底分别是

  1. 类属性
  2. 数据描述符
  3. 实例属性
  4. 非数据描述符
  5. 找不到的属性触发 __getattr__()

例1:利用描述符实现参数类型检测

class Typed(object):
    
    def __init__(self, key, exc_type):
        self.key = key
        self.exc_type = exc_type

    def __get__(self, instance, owner):
        return instance.__dict__[self.key]
    
    def __set__(self, instance, value):
        # 先判断类型是否为期望类型,如果不是则报错
        if not isinstance(value, self.exc_type):
            raise TypeError
        instance.__dict__[self.key] = value
    
    def __delete__(self, instance):
        instance.__dict__.pop(self.key)


class Person(object):
    name = Typed('name', str)
    age = Typed('age', int)

    def __init__(name, age):
        self.name = name
        self.age = age

例2:使用描述符自定制property

class Lazyproperty(object):

    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        '''使用该方法实现非数据描述符'''
        # 当使用类调用属性时,返回自身
        if not isinstance:
            return self
        ret = self.func(instance)
        # 如果该属性计算过程较为复杂,可以为实例设置属性,以后就不用重复计算了
        # 原因是因为非数据描述符优先级低于实例属性,下次调用的时候会优先从实例属性字典中查找
        # 而不会再次调用本方法重复计算
        setattr(instance, self.func.__name__, ret)
        return ret

class Room(object):

    def __init__(self, name, width, length):
        self.name = name
        self.width = width
        self.length = length
    
    @Lazyproperty # 这一步相当于定义了一个类属性 -> area = Lazyproperty(area)
    def area(self):
        return self.width * self.length
posted @ 2021-07-06 10:48  kingron  阅读(99)  评论(0编辑  收藏  举报