描述符是对多个属性运用相同存取逻辑的一种方式。例如,Django ORM 和 SQL Alchemy
等 ORM 中的字段类型是描述符,把数据库记录中字段里的数据与 Python 对象的属性对应
起来。
描述符是实现了特定协议的类,这个协议包括 __get__、__set__ 和 __delete__ 方
法。property 类实现了完整的描述符协议。通常,可以只实现部分协议。其实,我们在
真实的代码中见到的大多数描述符只实现了 __get__ 和 __set__ 方法,还有很多只实现
了其中的一个。

描述符是 Python 的独有特征,不仅在应用层中使用,在语言的基础设施中也有用到。除
了特性之外,使用描述符的 Python 功能还有方法及 classmethod 和 staticmethod 装饰
器。理解描述符是精通 Python 的关键。本章的话题就是描述符。

实现了 __get__、__set__ 或 __delete__ 方法的类是描述符。描述符的用法是,创建
一个实例,作为另一个类的类属性。

class Quantity:

    def __init__(self, storage_name):
self.storage_name = storage_name def __set__(self, instance, value):
if value > 0:
instance.__dict__[self.storage_name] = value
else:
raise ValueError('value must be > 0') class LineItem:
weight = Quantity('weight')
price = Quantity('price') def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price def subtotal(self):
return self.weight * self.price

改进版

class Quantity:
__counter = 0 def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index)
cls.__counter += 1 def __get__(self, instance, owner):
return getattr(instance, self.storage_name) def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value)
else:
raise ValueError('value must be > 0') class LineItem:
weight = Quantity()
price = Quantity() def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price def subtotal(self):
return self.weight * self.price

使用特性工厂函数实现

def quantity():
try:
quantity.counter += 1
except AttributeError:
quantity.counter = 0 storage_name = '_{}:{}'.format('quantity', quantity.counter) def qty_getter(instance):
return getattr(instance, storage_name) def qty_setter(instance, value):
if value > 0:
setattr(instance, storage_name, value)
else:
raise ValueError('value must be > 0') return property(qty_getter, qty_setter)

更深入的例子

class AutoStorage:#最基础的类,负责存储属性获取属性值和设置属性值
__counter = 0 def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}#{}'.format(prefix, index)
cls.__counter += 1 def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name) def __set__(self, instance, value):
setattr(instance, self.storage_name, value) class Validated(abc.ABC, AutoStorage):#抽象基类,子类必须实现validate方法,用来验证设置值是否有效 def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value) @abc.abstractmethod
def validate(self, instance, value):
"""return validated value or raise ValueError""" class Quantity(Validated):# 验证设置值是否大于0的验证类 def validate(self, instance, value):
if value <= 0:
raise ValueError('value must be > 0')
return value class NonBlank(Validated):# 验证设置值是否为空的验证类 def validate(self, instance, value):
value = value.strip()
if len(value) == 0:
raise ValueError('value cannot be empty or blank')
return value

覆盖型描述符
实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现
__set__ 方法的话,会覆盖对实例属性的赋值操作

特性也
是覆盖型描述符:如果没提供设值函数,property 类中的 __set__ 方法会抛出
AttributeError 异常,指明那个属性是只读的。

没有 __get__ 方法的覆盖型描述符
通常,覆盖型描述符既会实现 __set__ 方法,也会实现 __get__ 方法,不过也可以只实
现 __set__ 方法,如示例 20-1 所示。此时,只有写操作由描述符处理。通过实例读取描
述符会返回描述符对象本身,因为没有处理读操作的 __get__ 方法。如果直接通过实例
的 __dict__ 属性创建同名实例属性,以后再设置那个属性时,仍会由 __set__ 方法插
手接管,但是读取那个属性的话,就会直接从实例中返回新赋予的值,而不会返回描述符
对象。也就是说,实例属性会遮盖描述符,不过只有读操作是如此

非覆盖型描述符
没有实现 __set__ 方法的描述符是非覆盖型描述符。如果设置了同名的实例属性,描述
符会被遮盖,致使描述符无法处理那个实例的那个属性。方法是以非覆盖型描述符实现

描述符用法建议

使用特性以保持简单
  内置的 property 类创建的其实是覆盖型描述符,__set__ 方法和 __get__ 方法都
实现了,即便不定义设值方法也是如此。特性的 __set__ 方法默认抛出
AttributeError: can't set attribute,因此创建只读属性最简单的方式是使用特
性,这能避免下一条所述的问题。

只读描述符必须有 __set__ 方法
  如果使用描述符类实现只读属性,要记住,__get__ 和 __set__ 两个方法必须都定
义,否则,实例的同名属性会遮盖描述符。只读属性的 __set__ 方法只需抛出
AttributeError 异常,并提供合适的错误消息。

用于验证的描述符可以只有 __set__ 方法
  对仅用于验证的描述符来说,__set__ 方法应该检查 value 参数获得的值,如果有
效,使用描述符实例的名称为键,直接在实例的 __dict__ 属性中设置。这样,从实例中
读取同名属性的速度很快,因为不用经过 __get__ 方法处理

仅有 __get__ 方法的描述符可以实现高效缓存
  如果只编写了 __get__ 方法,那么创建的是非覆盖型描述符。这种描述符可用于执
行某些耗费资源的计算,然后为实例设置同名属性,缓存结果。同名实例属性会遮盖描述
符,因此后续访问会直接从实例的 __dict__ 属性中获取值,而不会再触发描述符的
__get__ 方法。
非特殊的方法可以被实例属性遮盖
  由于函数和方法只实现了 __get__ 方法,它们不会处理同名实例属性的赋值操作。
因此,像 my_obj.the_method = 7 这样简单赋值之后,后续通过该实例访问
the_method 得到的是数字 7——但是不影响类或其他实例。然而,特殊方法不受这个问
题的影响。解释器只会在类中寻找特殊的方法,也就是说,repr(x) 执行的其实是
x.__class__.__repr__(x),因此 x 的 __repr__ 属性对 repr(x) 方法调用没有影响。
出于同样的原因,实例的 __getattr__ 属性不会破坏常规的属性访问规则。

流畅的python第二十章属性描述符学习记录的更多相关文章

  1. 流畅的python第五章一等函数学习记录

    在python中,函数是一等对象,一等对象是满足以下条件的程序实体 1在运行时创建 2能复制给变量或数据结构的元素 3能作为参数传给函数 4能作为函数的返回结果 高阶函数(接受函数作为参数或者把函数作 ...

  2. Python:高级主题之(属性取值和赋值过程、属性描述符、装饰器)

    Python:高级主题之(属性取值和赋值过程.属性描述符.装饰器) 背景 学习了Javascript才知道原来属性的取值和赋值操作访问的“位置”可能不同.还有词法作用域这个东西,这也是我学习任何一门语 ...

  3. Python 属性描述符和属性的查找过程

    属性描述符可以用来控制给属性赋值的时候的一些行为 import numbers class IntField: def __get__(self, instance, owner): return s ...

  4. python之属性描述符与属性查找规则

    描述符 import numbers class IntgerField: def __get__(self, isinstance, owner): print('获取age') return se ...

  5. python 属性描述符

    import numbers class IntField: # 一个类只要实现了这个魔法函数,那么它就是属性描述符 #数据描述符 def __get__(self, instance, owner) ...

  6. Python属性描述符

    实现了__get__.set.__delete__中任意一个方法的类,称之为属性描述符. 属性描述符可以控制属性操作时的一些行为. 只要具有__get__方法的类就是描述符类. 如果一个类中具有__g ...

  7. 深入理解javascript对象系列第三篇——神秘的属性描述符

    × 目录 [1]类型 [2]方法 [3]详述[4]状态 前面的话 对于操作系统中的文件,我们可以驾轻就熟将其设置为只读.隐藏.系统文件或普通文件.于对象来说,属性描述符提供类似的功能,用来描述对象的值 ...

  8. JavaScript 属性描述符

    属性描述符(Property Descriptor)是 ES5 之后出现的概念,顾名思义,它用于描述属性应该是什么样,例如是否只读,能否枚举,能否可配置等.所有对象属性均可使用属性描述符来定义. 属性 ...

  9. JS属性描述符

    var myObject = { a:2 }; Object.getOwnpropertyDescriptor(myObject,"a"); { value:2, writable ...

随机推荐

  1. linux命令(4):vmstat命令

    CPU监控如下: vmstat 2 10  //表示每隔2秒运行10次 内存监控如下: vmstat –s 监控进程及CPU.内存状态 如下: top

  2. mac下安装golang

    1.安装homebrew ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/in ...

  3. C语言 ,两个字符串参数,判断是否包含另一个字符串,返回所在位置

    char * cyp(char *s1,char *s2) { char *p = NULL; char *q = NULL; char *q1 = NULL; while(*s1!='\0') { ...

  4. Populating Next Right Pointers in Each Node I&&II ——II仍然需要认真看看

    Populating Next Right Pointers in Each Node I Given a binary tree struct TreeLinkNode { TreeLinkNode ...

  5. [转载]Python logging模块详解

    原文地址: http://blog.csdn.net/zyz511919766/article/details/25136485 简单将日志打印到屏幕: import logging logging. ...

  6. Python3发送qq邮件,测试通过

    import smtplib from email.mime.text import MIMEText # 收件人列表 mail_namelist = ["10402852@qq.com&q ...

  7. Linux之父Linus的8个趣闻轶事

    博客中的文章均为 meelo 原创,请务必以链接形式注明本文地址 <只是为了好玩:Linux之父林纳斯自传>是一本很古老的书了,2001年就有了中文版,在2014的时候图灵图书又把它重新翻 ...

  8. Python基础系列----语法、数据类型、变量、编码

    1.基本语法                                                                                        Python ...

  9. cordova plugin 大全及安装命令

    前言:用cordova开发hybrid app的过程中,由于是html5开发的app,在手机上很多权限受限制,这就导致了我们需要安装很多插件来弥补这一缺点.在网上搜了好久,感觉都不是很全,所以自己整理 ...

  10. gulp之压缩css,less转css,浏览器实时刷新【原创】

    gulp入门 gulp浏览器实时同步 首先要下载对应的插件包: gulp-less包:cnpm install gulp-less --save-dev gulp-connect包:cnpm inst ...