• Python 描述符(Descriptor) 附实例


    在 Python 众多原生特性中,描述符可能是最少被自定义的特性之一,但它在底层实现的方法和属性却无时不刻被使用着,它优雅的实现方式体现出 Python 简洁之美。

    定义

    • 一个描述符是一个有"绑定行为"的对象属性(object attribute),它的访问控制会被描述器协议方法重写。
    • 任何定义了 __get__, __set__ 或者 __delete__ 任一方法的类称为描述符类,其实例对象便是一个描述符,这些方法称为描述符协议。
    • 当对一个实例属性进行访问时,Python 会按 obj.__dict__type(obj).__dict__type(obj)的父类.__dict__ 顺序进行查找,如果查找到目标属性并发现是一个描述符,Python 会调用描述符协议来改变默认的控制行为。
    • 描述符是 @property @classmethod @staticmethodsuper 的底层实现机制。

    特性

    • 同时定义了 __get____set__ 的描述符称为 数据描述符(data descriptor);仅定义了 __get__ 的称为 非数据描述符(non-data descriptor) 。两者区别在于:如果 obj.__dict__ 中有与描述符同名的属性,若描述符是数据描述符,则优先调用描述符,若是非数据描述符,则优先使用 obj.__dict__ 中属性。
    • 描述符协议必须定义在类的层次上,否则无法被自动调用。

    描述符协议

    __get__(self, instance, owner)

    _:param self: _描述符对象本身

    _:param instance: _使用描述符的对象的实例

    _:param owner: _使用描述符的对象拥有者

    __set__(self, instance, value)

    _:param value: _对描述符的赋值

    __delete__(self, instance)

    实例

    class LazyProperty(object):
        """
        实现惰性求值(访问时才计算,并将值缓存)
        利用了 obj.__dict__ 优先级高于 non-data descriptor 的特性
        第一次调用 __get__ 以同名属性存于实例字典中,之后就不再调用 __get__
        """
        def __init__(self, fun):
            self.fun = fun
    
        def __get__(self, instance, owner):
            if instance is None:
                return self
            value = self.fun(instance)
            setattr(instance, self.fun.__name__, value)
            return value
    
    class ReadonlyNumber(object):
        """
        实现只读属性(实例属性初始化后无法被修改)
        利用了 data descriptor 优先级高于 obj.__dict__ 的特性
        当试图对属性赋值时,总会先调用 __set__ 方法从而抛出异常
        """
        def __init__(self, value):
            self.value = value
    
        def __get__(self, instance, owner):
            return self.value
    
        def __set__(self, instance, value):
            raise AttributeError(
                "'%s' is not modifiable" % self.value
             )
    
    class Circle(object):
    
        pi = ReadonlyNumber(3.14)
    
        def __init__(self, radius):
            self.radius = radius
    
        @LazyProperty
        def area(self):
            print('Computing area')
            return self.pi * self.radius ** 2
    

    参考文章

    https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html


    欢迎关注

    微信公众号:面向人生编程

    编程思维不应只存留在代码之中,更应伴随于整个人生旅途,这个公众号不只聊技术,还会聊产品/互联网/经济学等广泛话题,所以也欢迎非程序员关注。

  • 相关阅读:
    游戏架构草稿(1)
    蔡学镛:架构师最重视的文档
    常见拉丁字母
    图像识别学习1
    .net framework 2.0,3.0与3.5之间的关系 [转载]
    ASP.NET Session丢失问题原因及解决方案[转载]
    PLSQL 循环游标 cursor loop fetch into【转载】
    oracle case when的用法 【转载】
    Oracle to_char格式化函数 [转载]
    oracle表关联应用 【转载】
  • 原文地址:https://www.cnblogs.com/zkqiang/p/10812313.html
Copyright © 2020-2023  润新知