描述符

前提是你 提交于 2019-12-03 21:33:28

 

一、简单理解一下描述符

python定义了把实现了__get__()、__set__()和__delete__()中的其中任意一种方法的类称之为描述符,描述符的本质是新式类,并且被代理的类(即应用描述符的类也是新式类)。描述符的作用是用来代理一个类的属性,需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例,我们通过查看实例和类的字典即可知晓。

描述符是可以实现大部分Python类特性中最底层的数据结构的实现手段,我们常使用的@classmethod、@staticmethd、@property、甚至是__slots__等属性都是通过描述符来实现的。它是很多高级库和框架的重要工具之一,是使用到装饰器或者元类的大型框架中的一个非常重要组件。在一般的开发中我们可能用不到描述符,但是我们如果想要开发一个大型的框架或者大型的系统,那使用描述符会起到如虎添翼的作用。它的加盟将会使得系统更加完美。下面将简单的介绍描述符的使用。

    class Descriptors:
        """
        数据描述符
        """
        def __init__(self, key, expected_type):
            """
            key:          用户传进的值
            expected_type:用户传进的值的类型
            """
            self.key = key
            self.expected_type = expected_type

        """
        描述符的三个内置属性的参数如下:
        ---------------------------------------------------
        self:     是描述符的对象,不是使用描述符类的对象
        instance: 这才是使用描述符类的对象
        owner:    是instance的类
        value:    是instance的值
        ---------------------------------------------------
        """

        def __get__(self, instance, owner):
            print("执行Descriptors的get")
            return instance.__dict__[self.key]    #将参数存入实例的字典

        def __set__(self, instance, value):
            print("执行Descriptors的set")
            #如果用户输入的值和值的类型不一致,则抛出TypeError异常
            if not isinstance(value, self.expected_type):
                raise TypeError("参数%s必须为%s"%(self.key, self.expected_type))
            instance.__dict__[self.key] = value   #为实例字典的key设值

        def __delete__(self, instance):
            print("执行Descriptors的delete")
            instance.__dict__.pop(self.key)       #删除实例字典的key

    class Light:

        #使用描述符
        name = Descriptors("name",str)
        price = Descriptors("price",float)

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

    #设置两个参数,触发两次set的执行
    light = Light("电灯泡", 66.66)
    print(light.__dict__)
    light.name = "火箭筒"
    print(light.__dict__)
    """
    执行Descriptors的set
    执行Descriptors的set
    {'name': '电灯泡', 'price': 66.66}
    执行Descriptors的set
    {'name': '火箭筒', 'price': 66.66}
    """

 

 

二、描述符的种类和优先级

 

 

 之所以要区分描述符的种类,主要是因为它在代理类属性时有着严格的优先级限制。例如当使用数据描述符时,因为数据描述符大于实例属性,所以当我们实例化一个类并使用该实例属性时,该实例属性已被数据描述符代理,此时我们对该实例属性的操作是对描述符的操作。描述符的优先级的高低如下:

       类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到的属性触发__getattr__()

 

1. 类属性 > 数据描述符

在以下测试用例中,使用 Light.name = "电灯泡" 语句没有触发set的执行说明类属性的优先级大于数据描述符的优先,此时相当于类属性覆盖了数据描述符,从而说明对类属性的一切操作都与描述符无关。

    class Descriptors:
        """
        数据描述符
        """
        def __get__(self, instance, owner):
            print("执行Descriptors的get")
        def __set__(self, instance, value):
            print("执行Descriptors的set")
        def __delete__(self, instance):
            print("执行Descriptors的delete")

    class Light:
        #使用描述符
        name = Descriptors()


    #测试
    Light.name               #执行描述符的get内置属性
    print(Light.__dict__)    #此时的name显示的是描述符的对象
    Light.name = "电灯泡"     #没有执行描述符的set内置属性
    print(Light.name)        #输出:电灯泡
    del Light.name           #没有执行描述符的delete内置属性
    print(Light.name)        #报错,因为Light类中的name被删了

 

 

2. 数据描述符 > 实例属性

 在以下用例测试中,数据描述符的优先级大于实例属性的优先级,此时实例属性name被数据描述符所覆盖,而price没有描述符代理,所以它任然是实例属性。

    class Descriptors:
        """
        数据描述符
        """
        def __get__(self, instance, owner):
            print("执行Descriptors的get")
        def __set__(self, instance, value):
            print("执行Descriptors的set")
        def __delete__(self, instance):
            print("执行Descriptors的delete")

    class Light:

        #使用描述符
        name = Descriptors()

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


    #使用类的实例对象来测试
    light = Light("电灯泡",60)       #执行描述符的set内置属性
    light.name                      #执行描述符的get内置属性
    print(light.__dict__)           #查看实例的字典,不存在name
    print(Light.__dict__)           #查看类的字典,存在name(为描述符的对象)
    del light.name                  #执行描述符的delete内置属性

 

 

3. 实例属性 > 数据描述符

 在以下用例测试中,如果我们的实例属性中使用了非数据描述符,就不能对其进行复制操作。可见非数据描述符应该应用于不需要设置值的属性或者函数中。上述的设计没有多大的意义,只是增加对描述符的理解。

    class Descriptors:
        """
        非数据描述符
        """
        def __get__(self, instance, owner):
            print("执行Descriptors的set")
        def __delete__(self, instance):
            print("执行Descriptors的delete")
    
    class Light:

        #使用描述符
        name = Descriptors()

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

    #测试
    light = Light("电灯泡",60)   #报错,描述符中没有__set__()方法

 

经以下测试用例证明在该类中并没有set方法,所以该类是一个非数据描述符,Python中一切皆对象,函数也是一个对象,既然是对象那也可以是类实例化所得到的结果。函数在类中本身也是一种属性(函数属性),描述符在应用的时候也是被实例化为一个属性。

    class Descriptors:
        """
        非数据描述符
        """
        def func(self):
            print("世界的变化真快!近日00后都已经开始在街头打小三了")

    d = Descriptors()
    d.func()
    print(hasattr(Descriptors.func,"__set__"))     #False
    print(hasattr(Descriptors.func,"__get__"))     #True
    print(hasattr(Descriptors.func,"__delete__"))  #False
    d.func = "函数也是属性,也可以赋值,没毛病"
    print(d.func)
    del d.func
    d.func

 

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!