转自:https://blog.tonyseek.com/post/notes-about-python-descriptor/

Python 引入的“描述符”(descriptor)语法特性真的很黄很暴力,我觉得这算是 Python 对象模型的核心成员之一。Python 语言设计的紧凑很大程度上得益于它。所以写一篇笔记文记录关于描述符我知道的一切。

低层 - 纯纯的描述符

纯纯的描述符很纯,基于类中定义的 __get__ 、 __set__ 、 __delete__ 三个特殊的方法。实现了这三个中方法的任意一个,这个类的实例就拥有了一些特殊特性。

假设现在有这么一个类 MyDescriptor,它拥有描述符的实现。把 MyDescriptor 实例化(my_descriptor),然后将实例作为另一个类 Spam 的类属性。

class MyDescriptor(object):
def __get__(self, subject_instance, subject_class):
return (self, subject_instance, subject_class) def __set__(self, subject_instance, value):
print "%r %r %r" (self, subject_instance, value) my_descriptor = MyDescriptor() class Spam(object):
my = my_descriptor spam = Spam()

吧 MyDescriptor 的实例作为 Spam 一个名为 my 的类属性之后,Spam 类中属性 my 的访问就被重载了。 Spam.my , spam.my 和 spam.my = 123 都不再直接在 spam.__dict__ 上获取和添加值,而是调用 my_descriptor.__get__(None, Spam) , my_descriptor.__get__(spam, Spam) 和 my_descriptor.__set__(spam, 123) 。如果 MyDescriptor 实现了 __delete__ ,那么 del spam.my 也会被重载为 my_descriptor.__delete__(spam) 。

所以我理解为对应“元类”(meta-class),描述符事实上是实现了“元属性”(meta-attribute)。通过这种重载方式,可以定义许多具有特殊行为的对象属性规格,而 Python 许多内置的对象模型成员也是通过这种方式实现的。

高层#0 - 从“函数”到“方法”

描述符在 Python 对象模型中的一个重要作用是实现“对象方法”(method)。我们都知道 Python 中方法有这些特性:

  • 方法本身是一个至少要有一个参数的普通函数,第 0 个参数位 self 指向实例,类似于 C++ 的 const T* this 指针。
  • 作为方法的函数在类的命名空间还是以普通函数的形式存在的,比如 Spam 类中的 egg 方法的原始形态是 Spam.__dict__['egg'] 这个函数。
  • 通过实例访问这个函数时,也就是类似 Spam().egg 的方式,得到的不再是 Spam.__dict__['egg'] 中保存的那个原始函数,而是一个包装过的 "bound method"。这个对象仍然有 __call__ 可以调用,但相比它所包装的原函数,这个对象接受调用时参数列表已经少了一个参数。这是一种类似 functools.partial(raw_function, self) 的效果,原函数已经被做了偏函数化处理,默认绑定了实例对象到第 0 个参数位(通常 self 所在的位置);
  • 奇怪的是,如果自己定义一个实现了 __call__ 的函数对象,把它放到类属性中,它并不会表现出“绑定 self”的特性。

我此前一直对此很疑惑,以为这是一个语法级别的行为。直到看了 Flask、Jinja 2 的作者 Armin Ronacher 的 一个 Presentation 我才知道,原来 Method 根本不是语法级别特性,而是通过描述符来实现的。

我此前一直忽略了的是:一个 def 定义的 Python 函数或一个 lambda 表达式,除了拥有 __call__ 实现外,还拥有 __get__ 实现。也就是说,所有 Python 函数都默认是一个描述符。这个描述符的特性在“类”这一对象上下文之外没有任何意义,但只要到了类中,就会和其他描述符所表现的一样,将 my_instance.my_method 重载为 MyClass.__dict__['my_method'].__get__(my_instance, MyClass) 。“绑定 self 参数” 这个过程正是 __get__ 的行为,导致了 spam.egg(1, 2, 3) 和 Spam.egg(spam, 1, 2, 3) 等价。

明白了这一点,我的思路就清晰多了。“方法”不就是一种特殊的类属性吗,而定义特殊类属性的行为在 Python 对象模型中的实现正是描述符。而对这点的理解也非常有用,Armin Ronacher 的演示稿中提到了可以利用这一点实现只对对象方法有效的装饰器。

高层#1 - Property 属性

不明白为啥属性这个词有 Property 和 Attribute 两种翻译,但是在 Python 中二者是有区别的。后者指的是真正的对象属性,就是保存在 obj.__dict__ 中的成员(我一般更喜欢用 vars(obj) 来取这个字典);而前者指的是类似 C# 中“方法属性”或 Java 中 Getter、Setter 方法的重载属性。

class Spam(object):

    @property
def egg(self):
return "some-value" @egg.setter
def egg(self, value):
self._egg = "value = %s" % value spam = Spam()

说起来也很清晰,就是 spam.egg 返回的是第一个 egg 方法调用的返回值, spam.egg = "abc" 调用的是对于第二个 egg 方法的 egg(self=spam, value="abc") 。此外还可以继续定义删除属性操作的重载。这个比对象方法要容易看出来得多, property 正是一个描述符。

高层#n - 不受限制的用户自定义描述符

除去语言内置的这些描述符用法,还有许多用户自定义的描述符用法。这些用法让开发者可以更加灵活地扩展对象行为,是非常有用的元编程工具。相比起元类对整个类行为的重载,描述符的重载粒度非常细,它只关注一个属性。所以使用上我们也可以比使用元类减少更多的顾忌,因为我们能非常容易看到它能量释放的边界。

用户定义描述符的例子,我觉得最经典的要属 SQLAlchemy (当然,Django Model 定义也是同理)。SQLAlchemy 的 Column 类就是描述符的实现者。定义一个模型对象的时候,以声明风格写出这个模型对应的数据表所拥有的属性:

class User(Jsonizable, LowerIdMixin, db.Model):
"""The account of the user.""" email = db.Column(db.String(64), unique=True, nullable=False)
nickname = db.Column(db.Unicode(20))

而 email 、 nickname 描述符将托管今后所有 User 的实例中,对于这两个同名属性的访问。SQLAlchemy 在映射对象关系的时候,可以用这个特性实现许多特性,比如延迟加载——直到访问user.roles 的时候才执行 SQL 语句查询 role 表并构造 Role 对象集返回。

Python 描述符(descriptor) 杂记的更多相关文章

  1. python描述符descriptor(一)

    Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...

  2. python描述符 descriptor

    descriptor 在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor.descriptor通常用来改 ...

  3. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解

     1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题 ...

  4. Python描述符 (descriptor) 详解

    1.什么是描述符? python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有 __get__(), __set__(), 和__delete__().如 ...

  5. Python 描述符(Descriptor) 附实例

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

  6. Python 描述符 (descriptor)

    1.什么是描述符? 描述符是Python新式类的关键点之一,它为对象属性提供强大的API,你可以认为描述符是表示对象属性的一个代理.当需要属性时,可根据你遇到的情况,通过描述符进行访问他(摘自Pyth ...

  7. python描述符descriptor(二)

    python内置的描述符 python有些内置的描述符对象,property.staticmethod.classmethod,python实现如下: class Property(object): ...

  8. 【python】描述符descriptor

    开始看官方文档,各种看不懂,只看到一句Properties, bound and unbound methods, static methods, and class methods are all ...

  9. 杂项之python描述符协议

    杂项之python描述符协议 本节内容 由来 描述符协议概念 类的静态方法及类方法实现原理 类作为装饰器使用 1. 由来 闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用 ...

随机推荐

  1. 渲染voronoi图

    渲染voronoi图要比计算voronoi图简单. 渲染voronoi图: 方法1: 在pixel shader里,对每一个像素,求哪个种子点到它的距离最近,将此种子点的颜色作为此像素颜色. 当种子点 ...

  2. Cg Programming/Vertex Transformations

    https://en.wikibooks.org/wiki/Cg_Programming/Vertex_Transformations

  3. Linux-Load blance

    在实际应用中,在Web服务器集群之前总会有一台负载均衡服务器,负载均衡设备的任务就是作为Web服务器流量的入口,挑选最合适的一台Web服务器,将客户端的请求转发给它处理,实现客户端到真实服务端的透明转 ...

  4. C# 使用ffmpeg.exe进行音频转换完整demo

    今天在处理微信的开发接口时候,发现微信多媒体上传接口中返回的音频格式是amr.坑人的是现在大部分的web 播放器,不支持amr的格式播放.试了很多方法都不行. 没办法,只要找一个妥协的解决方案:将am ...

  5. 目录操作工具类 CopyDir.java

    package com.util; import java.io.*; /** * 1,建立目的目录. 2,遍历源目录. 3,遍历过程中,创建文件或者文件夹. 原理:其实就是改变了源文件或者目录的目录 ...

  6. 在网页标题栏上和收藏夹显示网站logo

    第一步,准备一个图标制作软件. 首先您必须了解所谓的图标(Icon)是一种特殊的图形文件格式,它是以.ico 作为扩展名.普通的图像设计软件无法使用这种格式,所以您需要到下载一个ico图标工具,本站常 ...

  7. LintCode "Maximum Gap"

    Bucketing! A lot of details to take care. struct Bucket { Bucket() :l(-), r(-), bValid(false){}; int ...

  8. (转)C# wnform 请求http ( get , post 两种方式 )

    本文转载自:http://www.cnblogs.com/hailexuexi/archive/2011/03/04/1970926.html 1.Get请求 string strURL = &quo ...

  9. php获取一年中某一周的开始和结束时间

    PHP来获取一年中的每星期的开始日期和结束日期的代码 函数get_week()通过传入参数$year年份,获取当年第一天和最后一天所在的周数,计算第一周的日期,通过循环获取每一周的第一天和最后一天的日 ...

  10. 使用express搭建第一个Web应用【Node.js初学】

    来源:http://jingyan.baidu.com/article/bad08e1ee501e009c8512106.html express是一个开源的node.js项目框架,初学者使用expr ...