python 属性描述符

文章目录

    • 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__ 方法的描述符 可以实现 高效缓存
  • 非特殊的方法 可以被 实例属性遮盖

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/471694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机声音与视频教程,电脑怎么录屏幕视频带声音 电脑录屏幕视频带声音教程...

电脑怎么录屏幕视频带声音&#xff0c;相信有许多的用户在工作上或者学习上也会有这种需求&#xff0c;自己对此也还是不太的了解&#xff0c;毕竟也是没有怎么使用过&#xff0c;对此也是想要知道这电脑怎么录屏幕视频带声音&#xff0c;其实也是有几个原因&#xff0c;下面就…

tensor转换为图片_为大家介绍图片转换pdf的经验总结!你找对方法了吗?

图片转换pdf怎么做&#xff1f;不少朋友们在学习和工作中都发现了PDF这种文件格式似乎格外的吃香&#xff0c;你收到的很多培训文件和在网上搜罗的一些学习文件&#xff0c;全部都是PDF格式&#xff0c;PDF格式这么吃香&#xff1f;甚至有人让你把图片都转成PDF格式&#xff0c…

LeetCode 2021 力扣杯全国秋季编程大赛(第384名)

文章目录1. 无人机方阵2. 心算挑战3. 黑白翻转棋4. 玩具套圈5. 十字路口的交通2021.9.11&#xff0c;周六比赛之前&#xff1a;早上去交大看看&#xff0c;本科毕业10年了&#xff0c;由于限流&#xff0c;校园里没有多少回校的校友。逛了逛&#xff0c;跟太太和的她的同学一起…

java 画笔跟swing组件_Java学习教程(基础)--Java版本历史(二)

Java语言自JDK1.0版本以来经历了许多次更新&#xff0c;也在基本程序库中增加了大量的类别和包。从J2SE 1.4开始&#xff0c;Java语言的变动由 Java Community Process&#xff08;JCP&#xff09;管理&#xff0c;JCP使用Java规范请求&#xff08;Java Specification Requests…

计算机网络路由选择协议,IP路由选择协议原理和作用

IP路由选择协议原理和作用(2008-10-20 19:26:17)标签&#xff1a;杂谈IP路由选择如果目的主机与源主机直接相连或都在一个共享网络上,那就直接把包发送到目的主机,如果不是,那把ip数据报送到默认路由器,由它转发路由器使用路由表保存自己知道的网络的信息,它包括:目的IP地址,它…

LeetCode 2000. 反转单词前缀

文章目录1. 题目2. 解题1. 题目 给你一个下标从 0 开始的字符串 word 和一个字符 ch 。 找出 ch 第一次出现的下标 i &#xff0c;反转 word 中从下标 0 开始、直到下标 i 结束&#xff08;含下标 i &#xff09;的那段字符。 如果 word 中不存在字符 ch &#xff0c;则无需进…

oss图片跨域问题_图片存储解决方案-阿里云对象存储

开通对象存储OSS1. 打开阿里云官网&#xff0c;选择产品对象存储 OSS 2.开通对象存储OSS 需要支付宝扫码登录3.进入管理控制台 4.新建存储空间 5.跨域资源共享(CORS)的设置管理文件整合Springboot实现图片上传服务端签名直传并设置上传回调 : 在服务端完成签名&#xff0c;并且…

2015-8-10工作日志

1. 工作规划&#xff1a;完成系统请假管理的功能。 &#xff08;1&#xff09;根据系统需求完成请假管理model&#xff1b; &#xff08;2&#xff09;进行请假流程的deployment&#xff1b; &#xff08;3&#xff09;进行请假流程的流程定义管理&#xff1b; &#xff08;4&a…

LeetCode 2001. 可互换矩形的组数

文章目录1. 题目2. 解题1. 题目 用一个下标从 0 开始的二维整数数组 rectangles 来表示 n 个矩形&#xff0c;其中 rectangles[i] [widthi, heighti] 表示第 i 个矩形的宽度和高度。 如果两个矩形 i 和 j&#xff08;i < j&#xff09;的宽高比相同&#xff0c;则认为这两…

google 浏览器默认打开控制台_前端开发调试:浏览器console方法总结

今天突发奇想&#xff0c;准备总结下console的各个函数。以前都是只用一个console.log(),查了一下发现有好多&#xff0c;就记下来&#xff0c;方便以后查阅。速记consoleConsole对象提供浏览器控制台的接入&#xff0c;不同浏览器是不一样的&#xff0c;这里介绍普遍存在的Con…

江小白包装设计原型_雪碧和江小白的品牌跨界合作之旅可谓是一场品牌包装的视觉盛宴...

大家好&#xff0c;我是古小一&#xff0c;一个行走在酒水品牌包装设计不归路上的小编&#xff01;当下品牌间的跨界合作越来越多&#xff0c;消费者不但有审美疲劳的趋势&#xff0c;脑洞过大的跨界还容易引发群嘲。不过好在有热情网友的帮助&#xff0c;雪碧与江小白已经自然…

LeetCode 2002. 两个回文子序列长度的最大乘积(状态压缩+枚举状态子集+预处理)

文章目录1. 题目2. 解题2.1 超时2.2 预处理优化1. 题目 给你一个字符串 s &#xff0c;请你找到 s 中两个 不相交回文子序列 &#xff0c;使得它们长度的 乘积最大 。 两个子序列在原字符串中如果没有任何相同下标的字符&#xff0c;则它们是 不相交 的。 请你返回两个回文子…

[hdu5372 Segment Game]树状数组

题意&#xff1a;有两种操作&#xff1a;(1)插入线段&#xff0c;第i次插入的线段左边界为Li&#xff0c;长度为i (2)删除线段&#xff0c;删除第x次插入的线段。每次插入线段之前询问有多少条线段被它覆盖。 思路&#xff1a;由于插入的线段长度是递增的&#xff0c;所以第i次…

怎样从php转向java_Github标星10.8K!Java 实战博客项目分享

点击上方 Java后端&#xff0c;选择 设为星标优质文章&#xff0c;及时送达来源&#xff1a;开源最前线(ID&#xff1a;OpenSourceTop)作为程序员每天就是不停的敲代码&#xff0c;改Bug&#xff0c;写起代码来那真是行云如流水&#xff0c;但要你码出点文字&#xff0c;写点技…

Docker 部署一个用 Python 编写的 Web 应用

文章目录1. 安装 docker2. 编写代码3. 编写 Dockerfile4. 上传镜像5. 修改镜像learn from 《深入剖析Kubernetes》1. 安装 docker 在 WSL2 中安装 docker https://www.runoob.com/docker/ubuntu-docker-install.html 会报错&#xff1a; # Executing docker install script, …

python 代码文件路径注意事项

检查代码的工作路径&#xff0c;是不是代码文件所在路径 导入别处的包 基于该代码的工作路径写如下的代码&#xff0c;加入相对路径&#xff0c;可以找到要导入的文件 import sys import os sys.path.append("../xxx/") sys.path.append("../../xxx") fro…

微信小程序独立服务器的好处,微信小程序的优势和缺点

原标题&#xff1a;微信小程序的优势和缺点微信小程序从刚开始面世就引起了很多人的关注&#xff0c;虽然截止到目前&#xff0c;官方仍没有大肆宣传和推广&#xff0c;但是微信小程序的所带来的影响还是很大的&#xff0c;甚至让苹果&#xff0c;百度&#xff0c;阿里都头疼不…

LeetCode 2007. 从双倍数组中还原原数组(map)

文章目录1. 题目2. 解题1. 题目 一个整数数组 original 可以转变成一个 双倍 数组 changed &#xff0c;转变方式为将 original 中每个元素 值乘以 2 加入数组中&#xff0c;然后将所有元素 随机打乱 。 给你一个数组 changed &#xff0c;如果 change 是 双倍 数组&#xff…

怎么画韦布尔分布_手机按键寿命测试的样本数量怎么定?一文看懂 简述威布尔分布及其应用...

有一种手机按键的质量要求是&#xff0c;95%的产品的按压寿命需要达到1万次。根据这一要求&#xff0c;QC部门需要进行1.2万次的按压测试。那么QC部门需要选取多少按键来进行测试&#xff08;不允许有失效&#xff09;&#xff0c;才能保证95%的按键按压寿命达到1万次呢&#x…

Chrome浏览器报错:Origin null is not allowed by Access-Control-Allow-Origin.

问题&#xff1a;Chrome浏览器报错&#xff1a;Origin null is not allowed by Access-Control-Allow-Origin. 原因&#xff1a;.js文件中使用load()方法&#xff0c;而Chrome浏览器出于安全起见&#xff0c;不允许load本地文件。 方法&#xff1a;给Chrome添加启动参数--allow…