浅谈对属性描述符__get__、__set__、__delete__的理解

news2024/11/17 21:45:20

 1、属性描述符的基础介绍

1.1 何为属性描述符?

属性描述符是一种Python语言中的特殊对象,用于定义和控制类属性的行为。属性描述符可以通过定义__get__、__set__、__delete__方法来控制属性的读取、赋值和删除操作。

通过使用属性描述符,可以实现对属性的访问控制、类型检查、计算属性等高级功能。

如果一个对象定义了这些方法中的任何一个,它就是一个描述符。

看完上面的文字描述,是不是感觉一头雾水,没关系,接下来通过一个简单的案例来讲解属性描述符的作用。

1.2 为什么需要属性描述符?

假设我们现在要做一个成绩管理系统,在定义学生类时,我们可能这样写:

class Student(object):

    def __init__(self, name, age, cn_score, en_score):
        self.name = name
        self.age = age
        self.cn_score = cn_score
        self.en_score = en_score

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", 18, 70, 55)
print(xiaoming)
1.2.1 init函数中做参数校验

因为python是动态语言类型,不像静态语言那样,可以给参数指定类型,所以在传参时,无法得知参数是否正确。比如,当cn_score传入的值为字符串时,程序并不会报错。这个时候,一般就会想到对传入的参数做校验,当传入的参数不符合要求时,抛错。

class Student(object):

    def __init__(self, name, age, cn_score, en_score):
        self.name = name
        if not isinstance(age, int):
            raise TypeError("age must be int")
        if age <= 0:
            raise ValueError("age must be greater than 0")
        self.age = age

        if not isinstance(cn_score, int):
            raise TypeError("cn_score must be int")
        if 0 <= cn_score <= 100:
            raise ValueError("cn_score must be between 0 and 100")
        self.cn_score = cn_score

        if not isinstance(en_score, int):
            raise TypeError("en_score must be int")
        if 0 <= en_score <= 100:
            raise ValueError("en_score must be between 0 and 100")
        self.en_score = en_score

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)

虽然上面的代码可以实现参数校验,但是过多的逻辑判断在初始化函数里面,会导致函数特别臃肿,当增加新的参数时,需要增加逻辑判断,一方面重复代码增加,另外也不符合开闭原则

1.2.2 使用property做参数校验

这个时候该怎么处理呢,我们知道python的内置函数 property可用于装饰方法,使方法之看起来像属性一样。我们可以借助此函数来优化代码,优化后如下:

class Student(object):

    def __init__(self, name, age, cn_score, en_score):
        self.name = name
        self.age = age
        self.cn_score = cn_score
        self.en_score = en_score

    @property
    def age(self):
        return self.age

    @age.setter
    def age(self, value):
        if not isinstance(value, int):
            raise TypeError("age must be int")
        if value <= 0:
            raise ValueError("age must be greater than 0")
        self.age = value

    @property
    def cn_score(self):
        return self.cn_score

    @cn_score.setter
    def cn_score(self, value):
        if not isinstance(value, int):
            raise TypeError("cn_score must be int")
        if 0 <= value <= 100:
            raise ValueError("cn_score must be between 0 and 100")
        self.cn_score = value

    @property
    def en_score(self):
        return self.en_score

    @en_score.setter
    def en_score(self, value):
        if not isinstance(value, int):
            raise TypeError("en_score must be int")
        if 0 <= value <= 100:
            raise ValueError("en_score must be between 0 and 100")
        self.en_score = value

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)

现在代码看起来已经挺不错的了,确实。但是想想平常开发中,我们使用Diango 的 ORM 时,定义model时,只需要定义 modle 的属性,就可以使其完成参数的校验,比如ip = models.CharField(max_length=20, db_index=True, verbose_name='IP')。这是怎么做到的呢?

1.2.3 使用属性描述符做参数校验

其实,Django 是使用到了Python的属性描述符 __get__、__set__。接下来,我们使用上面的两个方法,来进行改造。代码如下:

class Score:
    def __init__(self, score):
        self.score = score

    def __get__(self, instance, owner):
        return self.score

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("value must be int")
        if 0 <= value <= 100:
            self.score = value
        else:
            raise ValueError("value must be between 0 and 100")


class Age:

    def __init__(self, age):
        self.age = age

    def __get__(self, instance, owner):
        return self.age

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("age must be int")
        if value <= 0:
            raise ValueError("age must be greater than 0")
        self.age = value


class Student(object):

    age = Age(0)
    cn_score = Score(0)
    en_score = Score(0)

    def __init__(self, name, _age, _cn_score, _en_score):
        self.name = name
        # 通过这里参数名称的区别,我们可以更加明确的知道,是调用
        self.age = _age
        self.cn_score = _cn_score
        self.en_score = _en_score

    def __str__(self):
        return "Student: {},age:{},cn_score:{},en_score:{}".format(self.name, self.age, self.cn_score, self.en_score)


xiaoming = Student("xiaoming", -1, 70, 55)
print(xiaoming)

通过上面的定义,也能够实现之前的功能,而且代码重用度更高,看起来也更加简洁。

1.3 属性描述符分类

常见的属性描述符包括数据描述符和非数据描述符。

  • 数据描述符

是指同时定义了__get__、__set__方法的属性描述符,它可以完全控制属性的读写操作。

  • 非数据描述符

是指只定义了__get__方法的属性描述符,它只能控制属性的读取操作,而不能控制属性的赋值和删除操作。

2、属性描述符的详细介绍

2.1 属性描述符的调用时机

描述符本质就是一个新式类,在这个新式类中,至少实现了__get__、__set__、__delete__中的一个,这也被称为描述符协议。

  • __get__():调用一个属性时,触发

  • __set__():为一个属性赋值时,触发

  • __delete__():采用del删除属性时,触发

通过下面的例子将更加清晰的知道 属性描述符的调用时机。

class Age:

    def __init__(self, age):
        self.age = age

    def __get__(self, instance, owner):
        print("coming __get__")
        return self.age

    def __set__(self, instance, value):
        print("coming __set__")
        if not isinstance(value, int):
            raise TypeError("age must be int")
        if value <= 0:
            raise ValueError("age must be greater than 0")
        self.age = value

    def __delete__(self, instance):
        print("coming __del__")
        del self.age


class Student(object):

    age = Age(0)

    def __init__(self, name):
        self.name = name


xiaoming = Student("xiaoming")
xiaoming.age = 9
print(xiaoming.age)
del xiaoming.age


#################
结果:
coming __set__
coming __get__
coming __del__

2.2 属性的搜索顺序

这里跟属性描述符关系不是特别大,主要是看看属性的搜索顺序。

默认的属性访问是从对象的字典中 get, set, 或者 delete 属性。例如a.x的查找顺序是:

a.__getattribute__() -> a.__dict__['age'] -> type(a).__dict__['age'] -> type(a)的基类(不包括元类)-> a.__getattr__ -> 抛错

如果查找的值是对象定义的描述方法之一,python可能会调用描述符方法来重载默认行为,发生在这个查找环节的哪里取决于定义了哪些描述符方法。

1、非数据描述器,实例的属性搜索顺序如下:

a.__getattribute__() -> a.__dict__['age'] -> a.__get__() -> type(a).__dict__['age'] -> type(a)的基类(不包括元类)-> a.__getattr__ -> 抛错 。

class Age(object):
    def __get__(self, instance, owner):
        print("coming __get__")
        return "__get__"

    # def __set__(self, instance, value):
    #     print("coming __set__")
    #     self.age = value


class A2(object):

    age = 10
    def __init__(self):

        self.age = 1000


class A(object):

    age = Age()

    def __init__(self):
        super().__init__()

    # def __getattribute__(self, item):
    #     print("coming __getattribute__")
    #     return "xxx"
    #
    def __getattr__(self, item):
        print("coming __getattr__")
        return "__getattr__"


a = A()
print(a.age)

2、数据描述器,实例的属性搜索顺序如下:

a.__getattribute__() -> a.__get__() -> a.__dict__['age'] -> type(a).__dict__['age'] -> type(a)的基类(不包括元类)-> a.__getattr__ -> 抛错 。

class Age(object):
    def __get__(self, instance, owner):
        print("coming __get__")
        return "__get__"

    def __set__(self, instance, value):
        print("coming __set__")
        self.age = value


class A2(object):


    def __init__(self):

        self.age = 1000


class A(object):

    age = Age()

    def __init__(self):
        self.age = 100
        super().__init__()

    # def __getattribute__(self, item):
    #     print("coming __getattribute__")
    #     return "xxx"
    #
    def __getattr__(self, item):
        print("coming __getattr__")
        return "__getattr__"


a = A()
print(a.age)

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

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

相关文章

2.4G芯片做遥控颈部按摩器方案

颈部按摩器很受上班族的欢迎&#xff0c;具有仿真人揉捏按摩效果&#xff0c;多单位力度调节&#xff0c;舒缓因长时间工作紧绷的的肌肉。主控芯片使用宇凡微的2.4g合封芯片。 一、颈部按摩器方案介绍 颈部按摩器方案的工作原理&#xff0c;主要采用电机驱动按摩触头&#xff0…

3.5 C++ 纯虚函数、抽象类 3.6 依赖倒转原则

纯虚函数 class A { public:virtual void print(){cout<<"A"<<endl;}virtual void test()0; //纯虚函数 }; 一个类内有纯虚函数&#xff0c;这个类就叫抽象类&#xff1b; 抽象类不能实例化&#xff1b; <java、python&#xff1a…

掌握最新的测评补单技术,了解速卖通、虾皮平台的风控机制

想要销量好&#xff0c;免不了要进行测评补单的&#xff0c;因为不管对于哪一个平台的新店铺新产品而言&#xff0c;前期只靠自然流量是很难的&#xff0c;速卖通平台也一样&#xff01;那么速卖通平台要如何进行测评补单呢&#xff1f; 市面上有许多不同的测评系统&#xff0c…

uniapp调查问卷评价功能

我本来用的是uniapp官方提供的组件uni-rate组件&#xff0c;但修改成我想要的样式有点麻烦&#xff0c;于是我就自己手写一个&#xff0c;比用组件简单一点&#xff1b; dom结构 <text class"formTit must">请您对本次活动进行评价</text> <view cl…

【word密码】word设置只读,如何取消?

Word文件打开之后发现是只读模式&#xff0c;那么我们如何取消word文档的只读模式呢&#xff1f;今天给大家介绍几种只读模式的取消方法。 属性只读 有些文件可能是在文件属性中添加了只读属性&#xff0c;这种情况&#xff0c;我们只需要点击文件&#xff0c;再次查看文件属…

爬虫012_字典高级操作_查询_修改_添加_删除和清空_遍历---python工作笔记031

然后来看字典高级,首先 打印某个元素 然后打印的时候注意,如果直接打印的值,在字典中没有就报错 这里要注意不能用点访问

集合工具类 Collections:提升集合操作效率

文章目录 多元素添加&#xff1a;addAll 方法随机置换&#xff1a;shuffle 方法自定义对象排序&#xff1a;sort 方法总结 在Java的集合框架中&#xff0c;Collections 是一个包含了许多操作集合的静态方法的工具类。通过使用 Collections 类提供的方法&#xff0c;我们能够更加…

SDU Crypto School - 计算不可区分性1

Encryption: Computational security 1-4 主讲人&#xff1a;李增鹏&#xff08;山东大学&#xff09; 参考教材&#xff1a;Jonathan Katz, Yehuda Lindell, Introduction to Modern Cryptography - Principles and Protocols. 什么是加密 首先&#xff0c;加密方案的目的在于…

【海拥工具】分享200+个关于AI的网站

给大家分享一个学习、摸鱼必备网站&#xff0c;我的好友海拥✘&#xff08;全网粉丝近20万&#xff0c;CSDN 内容合伙人&#xff0c;全栈领域优质创作者&#xff0c;华为云享专家&#xff0c;阿里云专家博主&#xff0c;InfoQ、蓝桥云课签约作者&#xff0c;HDZ核心组成员&…

石子游戏 dfs + 备忘录 JAVA

Alice 和 Bob 用几堆石子在做游戏。一共有偶数堆石子&#xff0c;排成一行&#xff1b;每堆都有 正 整数颗石子&#xff0c;数目为 piles[i] 。 游戏以谁手中的石子最多来决出胜负。石子的 总数 是 奇数 &#xff0c;所以没有平局。 Alice 和 Bob 轮流进行&#xff0c;Alice 先…

ArcGIS Pro基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例应用全流程科研能力提升

目录 第一章 入门篇 GIS理论及ArcGIS Pro基础 第二章 基础篇 ArcGIS数据管理与转换 第三章 数据编辑与查询、拓扑检查 第四章 制图篇 地图符号与版面设计 第五章 空间分析篇 ArcGIS矢量空间分析及应用 第六章 ArcGIS栅格空间分析及应用 第七章 影像篇 遥感影像处理 第八…

如何在Linux布置nginx(附带Nginx基本操作步骤)

文章目录 前言一、下载环境依赖二、下载nginx安装包三、具体操作流程总结 前言 提示&#xff1a;下述操作步骤适合内网服务器、局域网服务器和公网服务器。 不足之处欢迎交流指正&#xff0c;不喜勿喷。 一、下载环境依赖 yum -y install gcc zlib zlib-devel pcre-devel ope…

【升职加薪秘籍】我在服务监控方面的实践(4)-日志监控

大家好,我是蓝胖子&#xff0c;关于性能分析的视频和文章我也大大小小出了有一二十篇了&#xff0c;算是已经有了一个系列&#xff0c;之前的代码已经上传到github.com/HobbyBear/performance-analyze 接下来这段时间我将在之前内容的基础上&#xff0c;结合自己在公司生产上构…

CKA考试注意事项--考前必看!

一、考前 1. 核心&#xff0c;多练题&#xff0c;练到滚瓜烂熟&#xff0c;最好在1.5h内完成全部题目&#xff0c;有k8s基础练习两周即可&#xff0c;没基础可能要一个月。 2. 建议上TB购买一份题库&#xff08;95%与考试一致&#xff09;&#xff0c;并且会赠送题目k8s练习环…

[C++] 类与对象(中)完整讲述运算符重载示例 -- 日期类(Date) -- const成员

目录 1、前言 2、全缺省的构造函数 3、打印接口 4、拷贝构造 5、赋值运算符重载&#xff08;operator&#xff09; 5.1赋值重载是默认成员函数&#xff0c;重载格式&#xff1a; 5.2 赋值重载不能为全局函数 5.3 编译器默认生成 6、析构函数 7、operator> 8、ope…

Swift async/await 并发中如何将任务组(TaskGroup)转换为异步序列(AsyncSequence)

功能需求 在 Swift 新结构化并行模型的开发中, 提出了任务组(TaskGroup)和异步序列(AsyncSequence)的概念。有时候,为了简洁和效率方面的原因,我们需要将 TaskGroup 的结果转换为异步序列。 如上图所示,我们试图将任务组的结果转换为异步序列,但不幸失败了。 那么…

AI在线取英文名

AI在线取英文名 您是否曾经为了给自己、孩子或者您的产品取英文名字而苦恼&#xff1f;如何保留中文名字的音韵特点&#xff0c;同时又能传达名字的文化含义&#xff1f;别担心&#xff0c;我们为您准备了一款强大而创新的在线工具—— 中英名字创意匹配器&#xff0c;让您在命…

Ubuntu系统下库的相关问题解析

本文通过以下几个问题对Ubuntu系统下库的相关问题进行解析。 1.库是什么? 库是写好的&#xff0c;现有的&#xff0c;成熟的&#xff0c;可以复用的代码。现实中每个程序都要依赖很多基础的底层库&#xff0c;不可能每个人的代码都从零开始&#xff0c;因此库的存在意义非同…

ad+硬件每日学习十个知识点(27)23.8.7 (DCDC的datasheet)

文章目录 1.DCDC拿到手册需要关注的参数2.MOS选型3.TPS54331的ECO模式&#xff08;跳跃模式&#xff0c;损耗小、纹波大&#xff09;4.TPS54331的引脚功能&#xff08;BOOT这里没太看懂&#xff0c;之后回来再看看&#xff09;5.TPS54331的额定值6.TPS54331在test条件下的电气特…

多个配置WebMvcConfigurationSupport失效问题

最近在项目中用类继承WebMvcConfigurationSupport实现拦截器 Configuration RequiredArgsConstructor public class SpringWebSupport extends WebMvcConfigurationSupport {private final ProjectInterceptor projectInterceptor;// 拦截器 //设置拦截器对象和拦截请求Ove…