首页 > 其他分享 >浅谈对属性描述符__get__、__set__、__delete__的理解

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

时间:2023-04-12 23:11:57浏览次数:71  
标签:__ set 浅谈 age 描述符 self def 属性

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)

参考链接:

【案例讲解】Python为什么要使用描述符?

[属性描述符:__get__函数、__set__函数和__delete_函数](

标签:__,set,浅谈,age,描述符,self,def,属性
From: https://www.cnblogs.com/huageyiyangdewo/p/17311733.html

相关文章

  • 2023.4.12.汇报.pptx
    6月份汇报想法  8月份写论文 ==========================================================           ......
  • 欢乐商城源码/品云购商城源码/英文版商城源码/全开源 可二开
    demo软件园每日更新资源,请看到最后就能获取你想要的:1.欢乐商城源码/品云购商城源码/英文版商城源码/全开源可二开商城源码/英文版商城源码/全开源可二开出海项目源码后台为中文语言页面效果:2.SQL学习指南(第2版)这是一本关于SQL的书,不是关于数据库的。以MySQL为例来......
  • go 单文件上传,多文件上传
    单文件上传示例: main.gorouter:=router.InitRouter()router.Run() router/router.govarrouter=gin.Default()funcinit(){//加载自定义函数ifv,ok:=binding.Validator.Engine().(*validator.Validate);ok{v.RegisterValidation("capt",......
  • 移动端动态化的由来,你知道吗?
    目前的移动开发者面临的最大痛点就是面对极其复杂的环境,对此,庄卓然给出一个公式,移动开发的复杂度=应用数量×平台数量×要适配的各种各样的机型。如何解决这个问题呢?在解决问题之前,首先要对移动开发的未来有着精准的研判。阿里认为,移动开发的未来必定更加平衡,也就是说必须是性能与......
  • AlertDialog(对话框)详解
    本节继续给大家带来是显示提示信息的第三个控件AlertDialog(对话框),同时它也是其他Dialog的的父类!比如ProgressDialog,TimePickerDialog等,而AlertDialog的父类是:Dialog!另外,不像前面学习的Toast和Notification,AlertDialog并不能直接new出来,如果你打开AlertDialog的源码,会发现构造方法......
  • 【LeeCode】213. 打家劫舍 II
    【题目描述】你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。给定一个代表每个房......
  • mongoDB查询优化《一》
    同事反馈线上一个查询非常慢(10秒+),具体如下:1.问题语句db.myColl.find({app:"my_app",requestTime:{$gte:1492502247000,$lt:1492588800000}}).sort({_id:-1}).limit(1)表记录如下:{"app":"my_app","eventId":141761066,"requestTime":Numb......
  • 跟踪员工考勤时间和出勤率的四种方法
    用于跟踪员工考勤时间和出勤的程序可以为企业带来各种好处,它可以帮助管理人员掌握正在进行的项目及其完成状态,并让员工更好地了解某些任务需要多长时间,以便他们可以做出相应的计划。当您有效地跟踪员工的出勤时间和出勤率时,工资单处理和合规性也会变得容易很多。如果您想知道如何跟......
  • 四月学习之LVS基本概述
    一、LVS基本概述1、什么是LVSLVS的英文全称是linuxvirtualserver,即linux虚拟服务器,其实它是一种cluster集群技术,主要用于负载均衡,将用户请求均匀的调度到不同的服务器上执行注意:LVS是基于四层IP:PROT的负载均衡2、为何需要LVS1、解决七层端口数不够问题,实现百万连接2、解......
  • 基于短时幅度谱估计方法的数字语音信号增强matlab仿真
    1.算法仿真效果matlab2022a仿真结果如下:2.算法涉及理论知识概要语音处理过程中受到各种各样噪声的干扰,不但降低了语音质量,而且还将使整个系统无法正常工作。因此,为了消除噪声干扰,在现代语音处理技术中,工业上一般采用语音增强技术来改善语音质量从而提高系统性能。基于短时幅度......