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

yizhihongxing

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__ -> 抛错

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

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

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

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

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

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__ -> 抛错

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

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_函数

原文链接:https://www.cnblogs.com/huageyiyangdewo/p/17311733.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅谈对属性描述符__get__、__set__、__delete__的理解 - Python技术站

(0)
上一篇 2023年4月17日
下一篇 2023年4月17日

相关文章

  • Ubuntu 20.04最佳配置指南(新手必备)

    Ubuntu 20.04最佳配置指南(新手必备) 如果你是Ubuntu新手,想要将你的系统配置得更好,那么这篇指南是为你准备的。在这篇指南中,我们会介绍如何优化Ubuntu 20.04的配置,以提高其性能和易用性。我们将覆盖以下主题: 更新和升级软件 安装新的软件包和库 配置终端 更改默认设置 安装和使用GNOME Shell扩展 美化桌面 更新和升级软件 …

    python 2023年5月20日
    00
  • Python中的Function定义方法

    下面是Python中的函数定义方法的完整攻略: 定义函数 在Python中,函数的定义采用def关键字,其基本的语法结构如下: def 函数名(参数列表): 函数体 return 返回值 其中,参数列表中包含了函数调用时需要传入的参数,函数体中包含了函数需要执行的代码,return语句用于返回函数的结果。 以一个简单的例子来说明: def add(a, b)…

    python 2023年6月5日
    00
  • Python极值整数的边界探讨分析

    Python极值整数的边界探讨分析 在使用Python编程时,如果要处理数值比较大的情况,就需要了解Python支持的数据类型的极值整数的范围,避免数据丢失或发生错误。 整数数据类型 Python中的整数类型是int,它是一种可靠的数据类型,没有溢出的问题。Python的整数类型支持任意精度,理论上没有上限,因此整数类型可以表示非常大的数值。 最大值和最小值…

    python 2023年6月3日
    00
  • python线程优先级队列知识点总结

    Python线程优先级队列知识点总结 什么是线程优先级队列? 线程优先级队列是Python标准库中的一个模块,提供了一个可排序的、优先级队列的数据结构。 通常情况下,在多线程编程中,我们需要为线程分配不同的优先级,以确保执行时间更长、执行顺序更重要的任务被先处理。这就是优先级队列的作用。 使用线程优先级队列 在Python中,我们可以使用 queue 模块提…

    python 2023年6月3日
    00
  • 如何在Python中进行重复测量的方差分析

    进行重复测量的方差分析通常使用双因素方差分析方法(two-way ANOVA),其中包含两个自变量:实验条件和重复测量。下面是在Python中进行重复测量的方差分析的完整攻略。 1.准备数据集 首先需要准备数据集,其中包含了实验条件和重复测量的数据。需要将数据以适当的格式存储到文本文件或Excel文件中。 2.读取数据 使用Python中的pandas库读取…

    python-answer 2023年3月25日
    00
  • python 循环读取txt文档 并转换成csv的方法

    下面是使用Python循环读取txt文档并转换成CSV的攻略: 1. 确定文件路径和文件名 首先,要确定你的TXT文件的路径和名称,以及你转换后要保存CSV文件的路径和名称。在本文中,我们将假设 TXT 文件名为 example.txt,TXT文件所在的目录为 ./data/,我们将保存转换后的 CSV 文件到 ./output/ 目录下,命名为 outpu…

    python 2023年6月3日
    00
  • Python使用re模块验证危险字符

    以下是详细讲解“Python使用re模块验证危险字符”的完整攻略,包括危险字符的介绍、正则表达式的编写、代码实现、两个示例说明和注意事项。 危险字符介绍 在Web开发中,用户输入的数据可能包含一些危险字符,如SQL注入、XSS攻击等。为了防止这些攻击,我们需要对用户输入的数据进行验证和过滤。 下面是一些常见危险字符: SQL注入:’、”、;、–等。 XSS…

    python 2023年5月14日
    00
  • python列表元素拼接成字符串的4种方法

    以下是关于“python列表元素拼接成字符串的4种方法”的完整攻略。 方法1:使用join()函数 在Python中,可以使用join()函数将一个列表中的元素拼接成一个字符串。该函数定义在字符串类型中,用法如下: str = separator.join(iterable) 其中,separator为拼接的分隔符,iterable为被拼接的列表对象。下面是…

    python 2023年6月5日
    00
合作推广
合作推广
分享本页
返回顶部