转自: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. wikioi 1012最大公约数和最小公倍数【根据最大公约数和最小公倍数求原来的两个数a、b】

    /*====================================================================== 题目描述 输入二个正整数x0,y0(2<=x0& ...

  2. wikioi 1204 寻找子串位置

    /*======================================================================== 1204 寻找子串位置 题目描述 Descript ...

  3. 转载——Python模拟登录代码

    ''' Created on 2014-2-20 @author: Vincent ''' import urllib.parse import gzip import json import re ...

  4. 【转】C# Winform打包部署时添加注册表信息实现开机启动

    使用VS自带的打包模块可以很方便的对项目进行打包部署,同时我们也可以在安装部署时操作注册表实现开机启动软件.具体实现如下: 1.添加安装部署项目后,鼠标右键安装项目->视图->注册表,HK ...

  5. Swift技术之如何在iOS 8下使用Swift设计一个自定义的输入法 (主要是NSLayoutConstraint 的使用)

    当前位置: > Swift新手入门 > Swift技术之如何在iOS 8下使用Swift设计一个自定义的输入法 时间:2014-09-10 16:49来源:未知 作者:啊成 举报 点击:5 ...

  6. 数据结构线性表(js实现)

    最近在复习数据结构的过程中,发现基本上数据结构都是用C来实现的,自己之前学习的时候也是用C去写的,由于目前对js更为熟悉一些,所以这里选择使用js去实现其中的某些算法和结构.实际上算法和语言关系不大, ...

  7. 编写使用SpringSecurity的JUnit测试提醒

    近日在使用SpringSecurity的项目中发现一个小问题,就是在接口上加了@Secured标注限制调用接口权限时,某些JUnit无法正常调用了. 例如: @Secured(PrivilegeDAO ...

  8. ORACLE 常用数值函数

    1 ABS(n)返回数值弄参数的绝对值.它接受一个数值型值作为输入参数,或者任何可以隐式地转换为数值型值的值.并且返回数值型值的绝对值. Select abs(-1) from dual ABS(-1 ...

  9. Android 抽屉类SlidingDrawer的使用

     比较简单,设置好SlidingDrawer控件的handle和content属性就可以了.  android:content="@+id/content"  android:ha ...

  10. iptables基础信息介绍

    在linux系统下,网络安全,除了有SElinux,另外就是iptables防火墙了,这个是用的最多也是功能非常强大的一个工具,今天就对其简单的架构上技术进行概要描述.让自己后续能够逻辑清晰的处理云环 ...