描述符是对多个属性运用相同存取逻辑的一种方式。例如,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. mysql中json数据的拼接方式

    SELECT CONCAT('[', GROUP_CONCAT( CONCAT('{"id":"',STRUCTURE_ID,'"'),',', CONCAT( ...

  2. 通过javascript进行UTF-8编码

    通过javascript进行UTF-8编码 javascript的字符集: javascript程序是使用Unicode字符集编写的.Unicode是ASCII和Latin-1的超集,并支持地球上几乎 ...

  3. requere.js优化js脚本加载方案,使用篇。

    require.config({ paths: { "jquery": "jquery-3.2.1", 'index':"index" }} ...

  4. CentOS7.5字体美化

    背景知识 有衬线 (Serif) 无衬线 (Sans Serif) 和等宽 (Monospace) 字型 1 有衬线 (Serif) 字型是比较正式的字体,比划粗细不一,在笔划的边缘有装饰部分(我的理 ...

  5. HDU 1097.A hard puzzle-快速幂/取模

    快速幂: 代码: ll pow_mod(ll a,ll b){      ll ans=;      while(b){          ==){              ans=ans*a%mo ...

  6. 160. Intersection of Two Linked Lists【Easy】【求两个单链表的第一个交点】

    Write a program to find the node at which the intersection of two singly linked lists begins. For ex ...

  7. hh:mm:ss时间格式那些事儿

    怎么把hh:mm:ss.45 时间格式换算成秒? 比较简单点的格式,比如hh:mm:ss是比较容易的,但是怎么样把hh:mm:ss.45,这样的格式,就是秒不是整数的时间格式换算成秒? ans:将时间 ...

  8. CNN的发展

    模型的建立过程: 1959年,Hubel & Wiesel发现动物视觉皮层中的细胞负责检测感受野(receptive fields)中的光线.论文:Receptive fields and f ...

  9. 关于 Unity WebGL 的探索(一)

    到今天为止,项目已经上线一个多月了,目前稳定运行,各种 bug 也是有的.至少得到了苹果的两次推荐和 TapTap 一次首页推荐,也算是结项后第一时间对我们项目的一个肯定. 出于各种各样的可描述和不可 ...

  10. Linux命令之which

    which [选项] [命令] 将命令的完整路径写入标准输出.具体是在环境变量PATH设置的目录里查找符合条件的文件,而环境变量PATH中保存了查找命令时需要遍历的目录. (1).选项 -v,-V,- ...