描述符是对多个属性运用相同存取逻辑的一种方式。例如,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. [PAT] 1146 Topological Order(25 分)

    This is a problem given in the Graduate Entrance Exam in 2018: Which of the following is NOT a topol ...

  2. Elasticsearch( 插件开发)

    elasticsearch5.2.2 插件开发(一) Scripting plugins:这个插件本质来说,就是会调用用户的脚本,所以可以执行任何的程序,举例的话,可以通过这个插件,支持javascr ...

  3. 手动安装gcc 4.8.5

    # 下载gcc wget ftp://ftp.gnu.org/gnu/gcc/gcc-4.8.5/gcc-4.8.5.tar.gz # 解压并进入目录 tar -zxvf gcc-.tar.gz cd ...

  4. [水煮 ASP.NET Web API2 方法论](1-6)Model Validation

    问题 想要 ASP.NET Web API 执行模型验证,同时可以和 ASP.NET MVC 共享一些验证逻辑. 解决方案 ASP.NET Web API 与 ASP.NET MVC 支持一样的验证机 ...

  5. Docker 常用命令收集

    查看Docker版本 docker version 查看 Image docker images 打包 Image docker save -o ‘packageName.tar’ ‘imageNam ...

  6. EL表达式无法显示Model中的数据

    后台程序通过Debug都能正常返回数据并封装到Model中.而在前台通过EL表达式取值时却是原样输出,如${cart.num}... ///展现我的购物车 @RequestMapping(" ...

  7. hdu 1874 畅通工程(spfa 邻接矩阵 邻接表)

    题目链接 畅通工程,可以用dijkstra算法实现. 听说spfa很好用,来水一发 邻接矩阵实现: #include <stdio.h> #include <algorithm> ...

  8. 为函数自定义bind方法实例页面

    HTML代码: <input id="button" type="button" value="点击我" /> <span ...

  9. vue-music 关于Search(搜索页面)--上拉加载

       建立搜索框组件页面,searchBox,组件接受一个可以自定义传入的placeholder 属性.input v-model 双向绑定数据关联到query 中, 在created中监听 quer ...

  10. 【PHPExcel实例】 php 导出 excel 实例

    CREATE TABLE `person` ( `) DEFAULT NULL, `name` ) DEFAULT NULL, `birthday` date DEFAULT NULL ) ENGIN ...