描述符是对多个属性运用相同存取逻辑的一种方式。例如,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. LCA上的RMQ模板算法

    How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  2. Merge Intervals——STL的应用

    Given a collection of intervals, merge all overlapping intervals. For example, Given [1,3],[2,6],[8, ...

  3. 前端读者 | 从一行代码里面学点JavaScript

    本文来自 @张小俊128:链接:http://www.html-js.com/article/A-day-to-learn-from-a-line-of-code-inside-the-JavaScr ...

  4. 编译python可以调用的dll

    如果经过stdcall声明的方法,如果不是用def文件声明的导出函数或者extern “C” 声明的话,编译器会对函数名进行修改;在编译加上extern C:这样就OK了:另外可以在PYTHON代码里 ...

  5. 将excel中的sheet1导入到sqlserver中

    原文地址:C#将Excel数据表导入SQL数据库的两种方法作者:windream 方式一: 实现在c#中可高效的将excel数据导入到sqlserver数据库中,很多人通过循环来拼接sql,这样做不但 ...

  6. [THINKPHP] 模板可用三元操作符

    <td>{$vo['realname'] ? $vo['realname'] : $vo['nickname']}</td>

  7. Mysql远程连接报错:SQL Error (1130): Host '192.168.6.128' is not allowed to connect to this MySQL server

    通过SQLyog连接linux中的MySQL报错问题:SQL Error (1130): Host '192.168.6.128' is not allowed to connect to this ...

  8. Flask实战第66天:celery实现异步任务

    Celery文档:http://docs.celeryproject.org Celery 通过消息进行通信,用专用的工作线程不断监视任务队列以执行新工作. Celery需要消息传输来发送和接收消息. ...

  9. 【webssh】shellinabox搭建

    shellinabox搭建 centos环境安装命令 # yum install epel-release # yum install shellinabox 启动与关闭: service shell ...

  10. 理解Python的迭代器(转)

    原文地址: http://python.jobbole.com/81916/ 另外一篇文章: http://www.cnblogs.com/kaituorensheng/p/3826911.html ...