文章目录

  • 1. 描述符示例:验证属性
  • 2. 自动获取储存属性的名称
  • 3. 继承改进
  • 4. 覆盖型与非覆盖型描述符对比
    • 4.1 覆盖型描述符
    • 4.2 没有 `__get__` 方法的覆盖型描述符
    • 4.3 非覆盖型描述符
    • 4.4 在类中覆盖描述符
  • 5. 描述符用法建议

learn from 《流畅的python》

1. 描述符示例:验证属性

  • 描述符是对多个属性 运用 相同存取逻辑的一种方式
  • 描述符是实现了 特定协议 的类,这个协议包括 __get__、__set__ 和 __delete__ 方法
    property 类实现了完整的描述符协议

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

class Quantity:def __init__(self, storage_name):self.storage_name = storage_name# storage_name 属性,# 这是托管实例中存储值的 属性的名称def __set__(self, instance, value):# self 是描述符 实例(即 LineItem.weight 或 LineItem.price)# instance 是 托管实例(LineItem 实例)if value > 0:instance.__dict__[self.storage_name] = value# 必须直接 处理托管实例的 __dict__ 属性;# 如果使用内置的 setattr 函数,# 会再次触发 __set__ 方法,导致无限递归# self.__dict__[self.storage_name] = value 错误写法#else:raise ValueError("value must be greater than 0")class LineItem:weight = Quantity('weight')# 描述符实例绑定给 weight 属性, 类属性,所有 LineItem实例共享price = Quantity('price')def __init__(self, description, weight, price):self.description = descriptionself.weight = weightself.price = pricedef subtotal(self):return self.weight * self.price

为了避免 price = Quantity('weight') 这样的错误:
采用如下改进:

2. 自动获取储存属性的名称

class Quantity:__counter = 0  # Quantity 类属性,统计实例数量def __init__(self):cls = self.__class__  # cls 是 Quantity 类的引用prefix = cls.__name__ # 'Quantity'index = cls.__counterself.storage_name = "_{}#{}".format(prefix, index)# 每个描述符的 名称是独一无二的,# 这种非法命名(#) ,内置的 getattr 和 setattr 可以接受cls.__counter += 1# storage_name 属性,# 这是托管实例中存储值的 属性的名称def __set__(self, instance, value):# self 是描述符 实例(即 LineItem.weight 或 LineItem.price)# instance 是 托管实例(LineItem 实例)if value > 0:setattr(instance, self.storage_name, value)else:raise ValueError("value must be greater than 0")def __get__(self, instance, owner):# owner 参数是托管类(如 LineItem)的引用,# 通过描述符从托管类中获取属性时用得到return getattr(instance, self.storage_name)class LineItem:weight = Quantity() # '_Quantity#0'# 描述符实例绑定给 weight 属性, 类属性,所有 LineItem实例共享price = Quantity()   # '_Quantity#1'def __init__(self, description, weight, price):self.description = descriptionself.weight = weightself.price = pricedef subtotal(self):return self.weight * self.pricecoconuts = LineItem('Brazilian coconut', 20, 17.95)
print(coconuts.weight, coconuts.price) # 20, 17.95
print(getattr(coconuts, '_Quantity#0'), getattr(coconuts, '_Quantity#1'))
# 20, 17.95
print(LineItem.weight)
# 这种调用方式
# 描述符的 __get__ 方法接收到的 instance 参数值是 None
# AttributeError: 'NoneType' object has no attribute '_Quantity#0'

但是上面的报错信息,让人困惑,如何修改,最好让 __get__ 方法返回描述符实例

def __get__(self, instance, owner):if instance is None:return selfreturn getattr(instance, self.storage_name)print(LineItem.weight)
# <__main__.Quantity object at 0x000001DCA2683D00>

3. 继承改进

import abcclass AutoStorge:__counter = 0  # 统计实例数量def __init__(self):cls = self.__class__  # cls 是 Quantity 类的引用prefix = cls.__name__  # NonBlank, Quantityindex = cls.__counterself.storage_name = "_{}#{}".format(prefix, index)# 每个描述符的 名称是独一无二的,# 这种非法命名(#) ,内置的 getattr 和 setattr 可以接受cls.__counter += 1# storage_name 属性,# 这是托管实例中存储值的 属性的名称def __set__(self, instance, value):# self 是描述符 实例(即 LineItem.weight 或 LineItem.price)# instance 是 托管实例(LineItem 实例)setattr(instance, self.storage_name, value) # 没有验证,交给Validateddef __get__(self, instance, owner):if instance is None:return selfreturn getattr(instance, self.storage_name)class Validated(abc.ABC, AutoStorge):def __set__(self, instance, value):value = self.validate(instance, value)#  __set__ 方法把验证操作委托给 validate 方法super().__set__(instance, value)# 然后把返回的 value 传给超类的 __set__ 方法,存储值@abc.abstractmethod # 在这个类中,validate 是抽象方法def validate(self, instance, value):"""to do"""class Quantity(Validated): # 继承自 Validated 类def validate(self, instance, value): # 检查非正数if value <= 0:raise ValueError("value should be positive")return valueclass NonBlank(Validated): # 继承自 Validated 类"""a string with at least one non-space character"""def validate(self, instance, value):value = value.strip() # 去除首尾空白if len(value) == 0:raise ValueError('value cannot be empty or blank')return valueclass LineItem:description = NonBlank() # '_NonBlank#0'weight = Quantity()  # '_Quantity#0'# 描述符实例绑定给 weight 属性, 类属性,所有 LineItem实例共享price = Quantity()  # '_Quantity#1'def __init__(self, description, weight, price):self.description = descriptionself.weight = weightself.price = pricedef subtotal(self):return self.weight * self.pricecoconuts = LineItem('  Brazilian coconut  ', 20, 17.95)
print(coconuts.weight, coconuts.price) # 20 17.95
print(getattr(coconuts, '_NonBlank#0')) #Brazilian coconut
print(getattr(coconuts, '_Quantity#0'), getattr(coconuts, '_Quantity#1'))
# 20 17.95
print(LineItem.weight) # <__main__.Quantity object at 0x0000012B2A9AF9A0>
  • 描述符的典型用途——管理 数据属性
    这种描述符也叫覆盖型描述符,因为描述符的 __set__ 方法使用托管实例中的同名属性覆盖(即插手接管)了要设置的属性

4. 覆盖型与非覆盖型描述符对比

通过实例读取属性时, 通常返回的是实例中定义的属性;
但是,如果实例中没有指定的属性, 那么会获取类属性。
而为实例中的属性赋值时,通常会在实例中创建属性,根本不影响类

### 辅助函数,仅用于显示 ###
#
def cls_name(obj_or_cls):cls = type(obj_or_cls)if cls is type:cls = obj_or_clsreturn cls.__name__.split('.')[-1]def display(obj):cls = type(obj)if cls is type:return '<class {}>'.format(obj.__name__)elif cls in [type(None), int]:return repr(obj)else:return '<{} object>'.format(cls_name(obj))def print_args(name, *args):pseudo_args = ', '.join(display(x) for x in args)print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))class Overriding:"""也称数据描述符或强制描述符"""def __get__(self, instance, owner):print_args('get', self, instance, owner)def __set__(self, instance, value):print_args('set', self, instance, value)class OverridingNoGet:"""没有``__get__``方法的覆盖型描述符"""def __set__(self, instance, value):print_args('set', self, instance, value)class NonOverriding:"""也称非数据描述符 或 非遮盖型描述符"""def __get__(self, instance, owner):print_args('get', self, instance, owner)class Managed:over = Overriding()over_no_get = OverridingNoGet()non_over = NonOverriding()def spam(self):print('-> Managed.spam({})'.format(display(self)))

4.1 覆盖型描述符

obj = Managed()
obj.over # obj.over 触发描述符的 __get__ 方法, 第二个参数的值是托管实例 obj
# -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)
Managed.over # Managed.over 触发描述符的 __get__ 方法,第二个参数 (instance)的值是 None
# -> Overriding.__get__(<Overriding object>, None, <class Managed>)
obj.over = 7 # 为 obj.over 赋值,触发描述符的 __set__ 方法,最后一个参数的 值是 7
# -> Overriding.__set__(<Overriding object>, <Managed object>, 7)
obj.over # 会触发描述符的 __get__ 方法
# -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)
obj.__dict__['over'] = 8 #  跳过描述符,直接通过 obj.__dict__ 属性设值
print(vars(obj))  # {'over': 8} #  确认值在 obj.__dict__ 属性中,在 over 键名下
obj.over # 然而,即使是名为 over 的实例属性,Managed.over 描述符仍会覆盖读取 obj.over 这个操作
# -> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)

4.2 没有 __get__ 方法的覆盖型描述符

  • 只实现 __set__ 方法,只有 写操作由描述符处理。
  • 通过实例读取描述符会返回 描述符对象本身因为没有处理读操作的 __get__ 方法。
  • 如果直接通过实例的 __dict__ 属性创建同名实例属性,以后再设置那个属性时,仍会由 __set__ 方法 插手接管,但是读取那个属性的话,就会直接从实例中返回新赋予的值,而不会返回描述符对象。也就是说,实例属性会遮盖描述符,不过 只有读操作是如此
# 这个覆盖型描述符没有 __get__ 方法,因此,obj.over_no_get 从 类 中获取描述符实例
print(obj.over_no_get)      # <__main__.OverridingNoGet object at 0x000001BC57E3FA90>
# 直接从托管类中读取描述符实例也是如此
print(Managed.over_no_get)  # <__main__.OverridingNoGet object at 0x000001BC57E3FA90>
# obj.over_no_get 赋值会触发描述符的 __set__ 方法
obj.over_no_get = 7
# -> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)
print(obj.over_no_get)      # <__main__.OverridingNoGet object at 0x000001BC57E3FA90>
#  通过实例的 __dict__ 属性设置名为 over_no_get 的实例属性
obj.__dict__['over_no_get'] = 9
# 现在,over_no_get 实例属性会遮盖描述符,但是只有 读 操作是如此
print(obj.over_no_get)  # 9
# 为 obj.over_no_get 赋值,仍然经过描述符的 __set__ 方法处理
obj.over_no_get = 7
# -> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)
# 但是读取时,只要有同名的实例属性,描述符就会被遮盖
print(obj.over_no_get)  # 9

4.3 非覆盖型描述符

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

# obj.non_over 触发描述符的 __get__ 方法,第二个参数的值是 obj
obj.non_over
# -> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)
# Managed.non_over 是非覆盖型描述符,因此没有干涉赋值操作的 __set__ 方法
obj.non_over = 7
print(obj.non_over) # 7 现在,obj 有个名为 non_over 的实例属性,把 Managed 类的同名 描述符属性遮盖掉
Managed.non_over # Managed.non_over 描述符依然存在,会通过 类 截获这次访问
# -> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>)
del obj.non_over # 如果把 non_over 实例 属性删除了
obj.non_over # 读取 obj.non_over 时,会触发类中描述符的 __get__ 方法;但要注意,第二个参数的值是托管实例
# -> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)

4.4 在类中覆盖描述符

  • 不管 描述符 是不是覆盖型,为 类属性 赋值 都能 覆盖 描述符
obj = Managed()
Managed.over = 1
Managed.over_no_get = 2
Managed.non_over = 3
print(obj.over, obj.over_no_get, obj.non_over) # 1 2 3

5. 描述符用法建议

  • 创建只读属性最简单的方式是 使用特性 property
  • 使用 描述符类 实现只读属性,要记住,__get__ 和 __set__ 两个方法必须都定义,否则,实例的同名属性会遮盖描述符
  • 用于 验证的 描述符 可以 只有 __set__ 方法
  • 仅有 __get__ 方法的描述符 可以实现 高效缓存
  • 非特殊的方法 可以被 实例属性遮盖

python 属性描述符相关推荐

  1. python 属性描述符_Python属性描述符(二)

    Python存取属性的方式特别不对等,通过实例读取属性时,通常返回的是实例中定义的属性,但如果实例未曾定义过该属性,就会获取类属性,而为实例的属性赋值时,通常会在实例中创建属性,而不会影响到类本身.这 ...

  2. 假装Python高手,你真的懂属性描述符类!

    Python学了好几年,发现功力还是那样,很多同学经常这样抱怨!都说Python入门容易,精通难,确实是这样的,每当我们打开一些牛逼的源码框架,进去看窥探大牛的源码,比如Python里面非常著名的Dj ...

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

    属性取值和赋值过程 一切皆是对象,类型也是对象. 对象包含一个__class__属性指向其所属类型. 对象包含一个__dict__属性指向其所包含的成员(属性和方法). 取值过程(下面是伪代码) 1 ...

  4. Python 29 描述符

    目录 描述符定义 描述符的种类和优先级 描述符的应用 描述符 + 类装饰器  (给 Person类添加类属性) 利用描述符自定义 @property property 补充 描述符定义 描述符是一种类 ...

  5. JavaScript(3)之——对象的属性描述符

      对象的属性描述符是一个初学者容易忽略但是非常重要的特性,像是vue的数据双向绑定就是用它做文章.且关于它的方法和属性也很多,今天我来总结一下. 属性描述符概述   对象的每个属性都具备了属性描述符 ...

  6. javascript --- 属性描述符

    从ES5开始,所有的属性都具备了属性描述符 var myObject = {a: 2 };Object.getOwnPropertyDescriptor(myObject, "a" ...

  7. Javascript高级教程:数据属性描述符与存储属性描述符

    属性描述符 let obj = {name: "ziu",age: 18 } Object.defineProperty(obj, "height", {val ...

  8. java中的scr是什么意思,javascrpt中属性描述符的理解与使用

    javascrpt中属性描述符的理解与使用 属性描述符是ES5出现的概念.顾名思义:它用于描述对象里面的属性应该是什么样,例如是否只读,能否可枚举,能否可配置等.怎样?好理解吧. 既然是对象里面的属性 ...

  9. 关于属性描述符PropertyDescriptor

    本文首发于本博客 猫叔的博客,转载请申明出处 前言 感谢GY丶L粉丝的提问:属性描述器PropertyDescriptor是干嘛用的? 本来我也没有仔细了解过描述符这一块的知识,不过粉丝问了,我就抽周 ...

最新文章

  1. 在对linux系统分区进行格式化时需要对磁盘簇(或i节点密度)的大小进行选择,请说明选择的原则。
  2. 用UDEV服务解决RAC ASM存储设备名
  3. 经典动态规划:0-1 背包问题
  4. 简明 Python 教程学习笔记_2_函数
  5. websocket连接mysql_websocket 使用 spring 的service层 ,进而调用里面的 dao层 来操作数据库 ,包括redis、mysql等通用...
  6. DUMP文件分析4:栈溢出
  7. Codeforces Round #321 (Div. 2) C. Kefa and Park dfs
  8. 3.8 Anchor Boxes
  9. 电脑端一些快捷开源创建平台
  10. .net vue漂亮登录界面_基于 electron-vue 开发的音乐播放器「实践」
  11. 高校里的你值得关注的最强大脑
  12. 研磨设计模式学习笔记1--简单工厂(SimpleFactory)
  13. 又一小米固件下载网站:xiaomifirmwareupdate
  14. 图像直方图以及直方图的应用
  15. 基于JAVAWEB的高校行政管理系统
  16. PNG8格式图片详解
  17. 日本华人IT派遣那点事儿(2)
  18. 最佳平方逼近 matlab,最佳平方逼近的Matlab
  19. retroarch java,跨平台模拟器 RetroArch
  20. java 阿里云接口实现发送短信验证码

热门文章

  1. JavaScript中的数学对象Math
  2. 获取当前ip_教程丨WIN10系统下设置固定IP或动态IP
  3. Javascript中NaN、null和undefinded的区别
  4. C# DateTime简单的定时器用法
  5. 在程序中设置infopath中的整型等域值时出错解决方法
  6. Visual Studio 2008 Shell(翻译)
  7. .NET下使用DataAdapter保存数据时,如何生成command语句及使用事务
  8. PostgreSQL体系架构
  9. 感觉stm32太简单是一种自负吗?
  10. 物联网开发者被疯抢,华为做了什么?