51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

关于 Python 描述符(Descriptor)

描述符是在 Python 2.2 版本就被引用的特性,然而作为"元老",却逐渐消失在 Python 教程的视野中。但当你了解它时,你就懂得了什么是 Python 的优雅之美。

什么是描述符 {#什么是描述符}

初识描述符 {#初识描述符}

描述符的定义并不好理解,不如我们先见识一下它的功能:

|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | hljs python class String(object): def __get__(self, instance, owner): return instance.__dict__.get('_string') def __set__(self, instance, value): instance._string = str(value) class Model(object): username = String() m = Model() m.username = 3154 print(m.username) # 打印: '3154' |

如果你接触过 SQLAlchemy 类似的 ORM 库,你一定对上述代码实现的功能很眼熟。

没错,我们借助描述符,实现了简单的类似于 ORM 类型转换的功能,无论对 username 赋值什么类型,都会被自动转换成字符串。

在最新的 Python 3.7 文档中这样介绍道:

一般地,一个描述符是一个包含 "绑定行为" 的对象,对其属性的存取被描述符协议中定义的方法覆盖。
这些方法有:__get__()__set__()__delete__()
如果某个对象中定义了这些方法中的任意一个,那么这个对象就可以被称为一个描述符。

  • 描述符是一个有"绑定行为"的对象属性(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)

实例 {#实例}

|------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | hljs python """ 实现惰性求值(访问时才计算,并将值缓存) 利用了 obj.__dict__ 优先级高于 non-data descriptor 的特性 第一次调用 __get__ 以同名属性存于实例字典中,之后就不再调用 __get__ """ class lazyproperty(object): def __init__(self, fun): self.fun = fun def __get__(self, instance, owner): print(self, instance, owner) if instance is None: return self value = self.fun(instance) setattr(instance, self.fun.__name__, value) return value class Circle(object): def __init__(self, radius): self.radius = radius @lazyproperty def area(self): print('Computing area') return 3.1415 * self.radius ** 2 |

|---------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | hljs python """ 实现只读属性(实例属性初始化后无法被修改) 利用了 data descriptor 优先级高于 obj.__dict__ 的特性 当试图对属性赋值时,总会先调用 __set__ 方法从而抛出异常 """ class readonly_property(object): def __init__(self, fun): self.fun = fun def __get__(self, instance, owner): return self.fun(instance) def __set__(self, instance, value): raise AttributeError( "'%s' is not modifiable" % self.fun.__name__ ) class Circle(object): def __init__(self, radius): self.radius = radius @readonly_property def pi(self): return 3.1415 |

参考文章 {#参考文章}

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

赞(2)
未经允许不得转载:工具盒子 » 关于 Python 描述符(Descriptor)