28. 描述符

一、什么是描述符

  如果一个类中有如下 3 个方法中的任意一个,那么这个类创建的对象,可以称为 描述符对象

object.__get__(self, instance, owner=None)
object.__set__(self, instance, value)
object.__delete__(self, instance)

  如果有另外一个类,这个类中有一个 类属性,这个类属性对应的是上面类创建的实例对象,我们称此时的这个类属性叫做 描述符。通常,描述符具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法覆盖。

# 定义一个类,让其创建的对象是描述符对象
class Name:def __init__(self):self__name = Nonedef __get__(self, instance, owner):print("__get__()方法被调用了!")print(f"self: {self}")                  # self是当前描述符对象print(f"instance: {instance}")          # instance获取属性值的那个实例对象print(f"owner: {owner}", end="\n\n")    # owner是instance的类return self.__namedef __set__(self, instance, value):print("__set__()方法被调用了!")print(f"self: {self}")                  # self是当前描述符对象print(f"insance: {instance}")           # instance是给属性赋值的时候的那个实例对象print(f"value: {value}", end="\n\n")    # value是要设置的属性值if isinstance(value, str):self.__name = valueelse:raise TypeError("必须是字符串")def __delete__(self, instance):print("__delete__()方法被调用了!")print(f"self: {self}")print(f"instance: {instance}", end="\n\n")del self.__name
class Person:# 定义一个类属性,它指向的是一个实例对象# 且这个对象中有__get__()/__set__()/__delete()# 因此,我们就称类属性name就是描述符name = Name()
# 使用Person创建一个对象
p = Person()
p.name = "Sakura"
print(p.name, end="\n\n")
del p.name

  当我们自定义一个类属性,且类属性是一个具有 __get__()/__set__()/__delete__() 3 个方法中任意实现一个的类创建的实例对象,那么在获取这个属性的时候,会自动调用 __get__() 方法,在设置类属性的时候,会自动变成调用 __set__() 方法,在删除这个类属性的时候,会自动变成调用 __delete__() 方法;看上去很像 @property 装饰器,实际上 @property 装饰器就是如下所述的方式实现的。

  当访问一个属性时,我们可以不直接给一个值,而是接一个描述器(描述符对象),让访问和修改设置时自动调用 __get__() 方法和 __set__() 方法。再在 __get__() 方法和 __set__() 方法中进行某种处理,就可以实现更改操作属性行为的目的,这就是描述器做的事情。简单的来说,通过描述符能够实现调用属性时,执行特定的方法。

如果将描述符对象赋值给实例属性,就相当于这个属性指向了一个普通的对象,只不过这个对象中 __get__()__set__()__delete__() 方法罢了,不会自动调用 __get__() 方法,而如果类属性是一个描述符,则会自动调用其 __get__() 方法。

二、描述符的调用机制

  在 Python 3 中,所有的类都继承 object 类。当我们调用一个属性时,会自动调用 object 类中的 __getattribute__() 方法,__getattribute__() 方法会起到拦截属性的作用,它会自动判断调用对象时普通数据还是描述符对象。如果 普通的属性,那么获取之后直接返回。如果是描述符,那么就调用其 __get__() 方法,这个方法的返回值当作这个属性的值。如果在调用 __getattribute__() 方法的时候,没有找到这个属性,那么它会调用 __getattr__() 方法来处理没有这个属性的情况,一般会产生一个异常。

一般来说,可以不重写 __getattribute__() 方法,调用默认继承类中中的即可,如果要是重写了,那么可能会导致当前类中的描述符失效。

三、数据描述符与非数据描述符

  同时定义 __get__()__set__() 方法的描述符称为 数据描述符(资料描述符),只定义 __get__() 方法的描述符称为 非数据描述符(非资料描述符);

  当属性名和描述符相同时,在访问这个同名属性时,如果是数据描述符就会先访问描述符,如果是非数据描述符就会先访问属性。

class M:def __init__(self):self.x = 1def __get__(self, instance, owner):print("get m here")return self.xdef __set__(self, instance, value):print("set m here")self.x = value + 1
class N:def __init__(self):self.x = 1def __get__(self, instance, owner):print("get n here")return self.x
class AA:m = M()             # 数据描述符n = N()             # 非数据描述符def __init__(self, m, n):self.m = m      # 属性m和数据描述符m名字相同,先访问数据描述符self.n = n      # 属性n和非数据描述符n名字相同,先访问属性
aa = AA(2, 5)
# 只有n没有m,因为数据描述符同名时,不会访问到属性,会直接访问描述符,所以属性里就查不到m这个属性
print(f"aa.__dict__: {aa.__dict__}")
# m和n都有   
print(f"AA.__dict__: {AA.__dict__}")
# 非数据描述符同名时调用的是属性,为传入的5   
print(f"aa.n: {aa.n}")
# 如果类访问,就调用的是描述符,返回self.x的值      
print(f"AA.n: {AA.n}")
# 其实在aa=AA(2,5)创建实例时,进行了属性赋值,其中相当于进行了aa.m=2
# 但是aa调用m时却不是常规地调用属性m,而是数据描述符m       
print(f"aa.m: {aa.m}")       

四、描述符的注意点

【1】、是否可以用实例属性定义描述符

  如果将描述符对象赋值给实例属性,就相当于这个属性指向了一个普通的对象,只不过这个对象中 __get__()__set__()__delete__() 方法罢了,不会自动调用 __get__() 方法,而如果类属性是一个描述符,则会自动调用其 __get__() 方法。

class Name:def __init__(self, name):self.__name = namedef __get__(self, instance, owner):print("__get__()方法被调用了")return self.__namedef __set__(self, instance, value):print("__set__()方法被调用了")if isinstance(value, str):self.__name = valueelse:raise TypeError("必须是字符串")
class Person:def __init__(self):self.name = Name
p = Person()
print(p.name)
p.name = 27185
print(p.name)

【2】、描述符的实例一定是类的属性

class MaxValue:def __init__(self, init_value, max_value):self.value = init_valueself.max_value = max_valuedef __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):self.value = min(self.max_value, value)
class Widget:volume = MaxValue(0, 10)
a = Widget()
print("a的默认volume值: ", a.volume)
a.volume = 12
print("a设置后的volume值:", a.volume)b = Widget()
print("b默认的volume值:", b.volume)

在这里插入图片描述

  当我们通过 a 对象设置 volume 属性时,由于 volume 属性是一个数据描述符,它会调用 MaxValue 类的 __set__() 方法,此时会将 MaxValue 类的 self.value 属性设置为 10,其中 self 对象是 MaxValue 的实例对象。

  然后,我们通过 b 对象访问 volume 属性时,由于 volume 属性是一个数据描述符,它会调用 MaxValue 类的 __get__() 方法,返回属性 self.value 的值,其中 self 对象还是之前的 MaxValue 的实例对象。由于之前通过 a 对象将 self.value 的值设置为 10,此时,我们通过 b 对象访问 volume 属性时,返回 10。

  此时,我们可以通过字典来解决这个问题。
在这里插入图片描述

class MaxValue:def __init__(self, init_value, max_value):self.value = init_valueself.max_value = max_valueself.data = dict()def __get__(self, instance, owner):if not instance:return selfreturn self.data.get(instance, self.value)def __set__(self, instance, value):self.data[instance] = min(self.max_value, value)

五、描述符的应用

5.1、实现@classmethod装饰器

class my_classmethod:def __init__(self, func):# func属性指向show_info原来指向的函数self.func = func# my_classmethod实现了__get__()方法# 因此my_classmethod是一个描述符# __get__()方法是一个闭包def __get__(self, instance, owner):print(f"self: {self}")print(f"instance: {instance}")print(f"owner: {owner}")def call(*args, **kwargs):# 实现原函数的功能# owner是Person类return self.func(owner, *args, **kwargs)return callclass Person:name = "unknown"# 等价于show_info = my_classmethod(show_info)# 执行完之后,description指向my_classmethod实例出的对象@my_classmethoddef description(cls):print(f"{cls.name}是一个人")# 使用Person创建一个对象
p = Person()
# p.description是一个描述符
# 它会自动调用my_classmethod类中的__get__()方法
p.description()

5.2、惰性计算

class LazyProperty:"""实现惰性计算(访问时才计算,并将值缓存)利用了object.__dict__优先于非数据描述符的特性第一次调用__get__()以同名属性存于实例字典中,之后就不再调用__get__()"""# func指向原来的area()方法def __init__(self, func):self.func = func# self指向LazyProperty的实例对象,当前对象# instance指向Cricle的实例对象,a# owner指向Cricle类对象def __get__(self, instance, owner):print("lazyproperty.__get__()")if instance is None:return self# self.func指向原来的area()方法# instance指向Cricle的实例对象,avalue = self.func(instance)# setattr()方法给instance(a)指向的实例对象添加一个属性# 属性的名是self.func.__name__(area),值是value(pi*radius**2)setattr(instance, self.func.__name__, value)return value
class ReadOnlyNumber:"""实现只读属性(实例属性初始化后无法被释放)利用了数据描述符优先级高于object.__dict__的特性当试图对属性赋值时,总会先调用__set__()方法从而抛出异常"""def __init__(self, value):self.value = valuedef __get__(self, instance, owner):return self.valuedef __set__(self, instance, value):raise AttributeError("'%s' is not modifiable" % self.value)
class Cricle:# ReadOnlyNumber实现了__get__()和__set__()方法# 因此,pi是一个描述符pi = ReadOnlyNumber(3.14)def __init__(self, radius):self.radisu = radius#@LazyProperty是一个装饰器,相当于area=LazyProperty(area)@LazyPropertydef area(self):print("computing area")return self.pi * self.radisu ** 2
a = Cricle(4)
# area指向LazyProperty的实例对象
# LazyProperty实现了__get__()和__set__()方法
# 因此area是一个描述符,当我们访问a.area会调用LazyProperty的__get__()方法
print(a.area)
# 第二次调用时a的对象已经添加了area属性
# 当属性名和描述符相同时,在访问这个同名属性时,如果是非数据描述符就会先访问属性。
print(a.area)

在这里插入图片描述

当属性名和描述符相同时,在访问这个同名属性时,如果是数据描述符就会先访问描述符,如果是非数据描述符就会先访问属性。

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

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

相关文章

CVE-2024-32709 WordPress —— Recall 插件存在 SQL 注入漏洞

漏洞描述 WordPress 是一款免费开源的内容管理系统,适用于各类网站,包括个人博客、电子商务系统、企业网站。其插件 WP-Recall 的 account 存在 SQL 注入漏洞,攻击者可以通过该漏洞获取数据库敏感信息。 WP-Recall 版本 <= 16.26.5 漏洞复现 搭建环境、安装插件、完成…

Flink CDC实时同步mysql数据

官方参考资料&#xff1a; https://nightlies.apache.org/flink/flink-cdc-docs-master/zh/docs/connectors/flink-sources/mysql-cdc/ Apache Flink 的 Change Data Capture (CDC) 是一种用于捕获数据库变化&#xff08;如插入、更新和删除操作&#xff09;的技术。Flink CDC…

Odoo:免费开源ERP的AI技术赋能出海企业电子商务应用介绍

概述 伴随电子商务的持续演进&#xff0c;客户对于便利性、速度以及个性化服务的期许急剧攀升。企业务必要探寻创新之途径&#xff0c;以强化自身运营&#xff0c;并优化购物体验。达成此目标的最为行之有效的方式之一&#xff0c;便是将 AI 呼叫助手融入您的电子商务平台。我们…

二、使用langchain搭建RAG:金融问答机器人--数据清洗和切片

选择金融领域的专业文档作为源文件 这里选择 《博金大模型挑战赛-金融千问14b数据集》&#xff0c;这个数据集包含若干公司的年报&#xff0c;我们将利用这个年报搭建金融问答机器人。 具体下载地址 这里 git clone https://www.modelscope.cn/datasets/BJQW14B/bs_challenge_…

maven使用Dependency-Check来扫描安全漏洞

在现代软件开发中&#xff0c;使用开源库和第三方依赖项已成为常态。然而&#xff0c;这些依赖项可能包含已知的安全漏洞&#xff0c;给应用程序带来潜在的风险。为了解决这个问题&#xff0c;OWASP Dependency-Check 应运而生。本文将介绍 OWASP Dependency-Check 的功能、安装…

meta-llama/Llama-3.2-1B 微调记录

踩坑&#xff1a; 1.刚开始部署在自己的windows电脑上&#xff0c;semgrep不支持windows &#xff0c;然后就换了linux服务器 2.服务器没有梯子&#xff0c;huggingface无法访问&#xff0c;模型数据集无法下载 解决方法&#xff1a; 使用huggingface镜像网站下载模型&#xf…

双指针---有效三角形的个数

这里写自定义目录标题 题目链接 [有效三角形的个数](https://leetcode.cn/problems/valid-triangle-number/description/)问题分析代码解决执行用时 题目链接 有效三角形的个数 给定一个包含非负整数的数组 nums &#xff0c;返回其中可以组成三角形三条边的三元组个数。 示例…

《通信电子电路》入门手册

因为大学这门课好多同学理解不了这门课 于是考完试后花了两天时间整理了这份笔记&#xff0c;在这分享给完全没有学懂这门课的同学&#xff0c;也帮助“理解概念才能学得进去”的同学入门 笔记&#xff1a;通信电子电路 入门手册 —— flowus笔记 对应&#xff1a;《通信电子…

基于单片机的智能水表的设计

1总体设计 本次设计智能IC卡水表&#xff0c;在系统架构上设计如图2.1所示&#xff0c;由STM32F103单片机&#xff0c;YF-S401霍尔型传感器&#xff0c;RFID模块&#xff0c;OLED12864液晶,按键&#xff0c;继电器等构成&#xff0c;在功能上可以实现水流量的检测&#xff0c;…

LA2016逻辑分析仪使用笔记1:测量引脚、解析串口数据

今日尝试学习使用LA2016逻辑分析仪&#xff1a;测量引脚 解析串口数据&#xff1a; 目录 逻辑分析仪&#xff1a; 实验接线&#xff1a; 基础操作&#xff1a; 选择使用到的通道&#xff1a; 设置采样时间、采样频率&#xff1a; 设置电平标准&#xff1a; 解析串口数据、测量串…

[论文阅读]Universal and transferable adversarial attacks on aligned language models

Universal and transferable adversarial attacks on aligned language models http://arxiv.org/abs/2307.15043 图 1&#xff1a;Aligned LLMs 不是对抗性 Aligned。我们的攻击构建了一个单一的对抗性提示&#xff0c;该提示始终绕过最先进的商业模式&#xff08;包括 ChatG…

【C++】小乐乐求和题目分析n变量类型讨论

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;解题分析&#x1f4af;为什么 n 需要是 long long问题重点&#xff1a;中间计算水平上的数据类型不足的例子&#xff1a;正确解决&#xff1a;将 n 设…

计算机基础知识——数据结构与算法(一)(山东省大数据职称考试)

大数据分析应用-初级 第一部分 基础知识 一、大数据法律法规、政策文件、相关标准 二、计算机基础知识 三、信息化基础知识 四、密码学 五、大数据安全 六、数据库系统 七、数据仓库. 第二部分 专业知识 一、大数据技术与应用 二、大数据分析模型 三、数据科学 数据结构与算法…

WebView通过@JavascriptInterface 调用原生方法

1. 创建 WebView 和设置 WebView 设置 在 XML 布局中添加 WebView 在activity_main.xml里创建一个WebView控件 <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schem…

基于AI对话生成剧情AVG游戏

游戏开发这个领域&#xff0c;一直有较高的学习门槛。作为一个非专业的游戏爱好者&#xff0c;如果想要开发游戏&#xff0c;往往受制于游戏引擎的专业程度&#xff0c;难以完成复杂的游戏项目。 AI IDE的诞生&#xff0c;提供了另外的一种思路&#xff0c;即通过AI 生成项目及…

ElasticSearch 数据聚合与运算

1、数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现数据的统计、分析和运算。实现这些统计功能的比数据库的 SQL 要方便的多&#xff0c;而且查询速度非常快&#xff0c;可以实现近实时搜索效果。 注意&#xff1a; 参加聚合的字段必须是 keywor…

F5中获取客户端ip地址(client ip)

当F5设备对其原始设置上的所有IP地址使用NAT时&#xff0c;连接到poo成员&#xff08;nodes、backend servers&#xff09;的出站连接将是NAT IP地址。 pool 成员&#xff08;nodes、backend servers&#xff09;将无法看到真实的客户端 ip地址&#xff0c;因为看到的是F5上的…

MATLAB引用矩阵元素的几种方法

引用矩阵元素可以通过索引&#xff0c;也可以通过逻辑值 索引 通过引用元素在矩阵中的位置来提取元素&#xff0c;例如&#xff1a; - 逻辑值 通过某种逻辑运算来使得要提取的值变为逻辑 1 1 1&#xff0c;用 A ( ) A() A()提取即可&#xff0c; A A A为原矩阵的名称。 例如&…

机器学习预处理-表格数据的空值处理

机器学习预处理-表格数据的空值处理 机器学习预处理-表格数据的分析与可视化中详细介绍了表格数据的python可视化&#xff0c;可视化能够帮助我们了解数据的构成和分布&#xff0c;是我们进行机器学习的必备步骤。上文中也提及&#xff0c;原始的数据存在部分的缺失&#xff0…

了解 SpringMVC 请求流程

文章目录 1. Spring 基础 - SpringMVC 请求流程1.1 引入1.2 什么是 MVC1.3 什么是 Spring MVC1.4 请求流程核心架构的具体流程步骤补充 1.5 案例**Maven 包引入****业务代码的编写**DaoServiceControllerwebapp 下的 web.xmlspringmvc.xmlJSP 视图 2. Spring 进阶 - Dispatcher…