首页 > 编程语言 >40、Python之面向对象:扩展的对象属性解析顺序(描述符 + MRO)

40、Python之面向对象:扩展的对象属性解析顺序(描述符 + MRO)

时间:2024-08-15 11:25:21浏览次数:10  
标签:__ Python self value 描述符 MRO 40 print 属性

引言

在上一篇文章中,我们简单回顾了Python中在继承语境下的属性解析顺序,同时补充了能够控制、影响属性解析的3个函数/方法(2个魔术方法 + 1个内置函数),相信对Python中属性的解析,相较于MRO,有了更进一步的认识。

今天这篇文章中,我们将考虑属性描述符存在的情况下,对于Python中的属性解析顺序又会产生怎样的影响,从而给出一个更加完整的实例对象的属性解析顺序。

属性描述符的种类

前面已经介绍过属性描述符的定义及使用,其实属性描述符根据所实现的魔术方法的不同,可以分为两种类型:

1、数据描述符:同时定义了__get__()和__set__()方法,或者定义了__set__()方法(仅定义__set__,其实没有太大意义)的属性描述符为“数据描述符”。

2、非数据描述符:仅定义了__get__()方法的描述符,称为“非数据描述符”。

接下来,我们分别定义一个数据描述符和非数据描述符,直接看代码:

# 定义一个数据描述符
class IntProperty:
    def __init__(self, min_value=None, max_value=None):
        self._value = None
        self.min_value = min_value
        self.max_value = max_value

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

    def __set__(self, instance, value):
        if self.min_value is not None and value < self.min_value:
            raise ValueError(f'必须要大于等于{self.min_value}')
        if self.max_value is not None and value > self.max_value:
            raise ValueError(f'必须要小于等于{self.max_value}')
        self._value = value


# 定义一个非数据描述符
class NotDataProperty:
    def __init__(self, value):
        self._value = value

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


class DaGongRen:
    count = 0
    age = IntProperty(18, 120)
    team = NotDataProperty('游兵散勇')

    def __init__(self, name):
        self.name = name
        DaGongRen.count += 1


class Programmer(DaGongRen):
    def __init__(self, name, gender):
        super().__init__(name)
        self.gender = gender


if __name__ == '__main__':
    coder = Programmer('张三', '女')
    print(coder.__dict__)
    # 数据描述符
    coder.age = 20
    print(coder.age)
    print(coder.__dict__)
    coder.__dict__['age'] = 35
    print(coder.__dict__)
    print(coder.age)
    print(coder.__dict__['age'])
    # 非数据描述符
    print(coder.team)
    print(coder.__dict__)
    coder.team = '正规组织'
    print(coder.__dict__)
    print(coder.team)
    print(coder.__dict__['team'])

执行结果:

d7cd92bfd31cd8c50ef18b0ff4135533.jpeg

代码中,我们分别定义了一个数据描述符和一个非数据描述符。其中,数据描述符是一个整数的属性描述符,用于控制属性的合法取值范围。非数据描述符定义了一个打工人所属组织的一个初始默认值,实现的功能是如果一个实例对象没有重新设置team,则始终返回默认值,一旦设置了team属性,则取属性自身的team取值,而不会影响到新的实例对象的取值。

通过代码及执行结果,我们可以大概得出以下结论:

1、数据描述符其实是将属性整个托管给描述符机制了,不管是对属性的访问还是修改,都是基于描述符实现的,相关的数据不会在实例对象的命名空间也就是__dict__字典中体现。

2、即使我们手动在实例对象的__dict__中显式添加一个与数据描述符同名的属性,通过“点”操作符访问到的仍然是数据描述符对应的属性。

3、不同于数据描述符的统一接管,非数据描述符只接管了属性的访问操作。而且,一旦对该属性进行了修改操作,则会在实例对象的命名空间__dict__字典中添加同名属性,后续对该属性的访问,都是对__dict__中的同名属性的访问及修改了。

完整的属性解析顺序

首先给出相对完整的属性解析顺序的结论,之后再通过代码进行演示验证结论。

当通过实例对象“点”操作符访问属性或者等价的getattr()内置函数的形式访问属性时,会按照以下顺序进行属性的解析:

1、首先调用__getattribute__()魔术方法,进行统一的属性访问控制逻辑的执行。

2、如果要访问的属性时数据描述符,则__getattriubte__()方法的内部会进行数据描述符__get__()方法的调用,返回相应的属性值,属性解析结束。

3、如果属性在实例对象的命名空间__dict__字典中,则直接返回,属性解析结束。

4、如果属性在实例对象所属类的命名空间,即__class__.__dict__字典中,则直接返回,属性解析结束。

5、如果属性在示例对象所属类的基类(按照MRO顺序进行解析查找)的__dict__字典中,则直接返回,属性解析结束。

6、如果存在同名的非数据描述符,则调用其__get__()方法,返回属性值,属性解析结束。

7、如果实例对象所属类有定义__getattr__()方法,则调用__getattr__()方法,属性解析结束。

8、属性解析失败,抛出AttributeError。

对应的流程图如下:

接下来,以一个完整的代码示例,来演示属性解析顺序:

# 定义一个数据描述符
class IntProperty:
    def __init__(self, min_value=None, max_value=None):
        self._value = None
        self.min_value = min_value
        self.max_value = max_value

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

    def __set__(self, instance, value):
        if self.min_value is not None and value < self.min_value:
            raise ValueError(f'必须要大于等于{self.min_value}')
        if self.max_value is not None and value > self.max_value:
            raise ValueError(f'必须要小于等于{self.max_value}')
        self._value = value


# 定义一个非数据描述符
class NotDataProperty:
    def __init__(self, value):
        self._value = value

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


class DaGongRen:
    count = 0
    age = IntProperty(18, 120)
    team = NotDataProperty('游兵散勇')

    def __init__(self, name):
        self.name = name
        DaGongRen.count += 1

    def __getattribute__(self, item):
        print(f"尝试访问属性{item}")
        return super().__getattribute__(item)


class Programmer(DaGongRen):
    def __init__(self, name, gender):
        super().__init__(name)
        self.gender = gender


def my_getattr(obj, item):
    print(f'属性{item}不存在')
    return None


if __name__ == '__main__':
    coder = Programmer('张三', '女')
    # 当前实例对象的命名空间
    print(coder.__dict__)
    # 所属类的命名空间
    print(Programmer.__dict__)
    # 所属类的基类的命名空间
    print(DaGongRen.__dict__)
    print(f"{'=' * 10} 1、访问普通实例属性name {'=' * 10}")
    print(coder.name)
    print(f"{'=' * 10} 2、访问普通类属性属性count {'=' * 10}")
    print(coder.count)
    print(f"{'=' * 10} 3、访问非数据描述符team {'=' * 10}")
    print(coder.team)
    print(f"{'=' * 10} 4、修改非数据描述符后再访问team {'=' * 10}")
    coder.team = '一个很正经的产研组织'
    print(coder.__dict__)
    print(coder.team)
    print(f"{'=' * 10} 5、访问数据描述符age {'=' * 10}")
    print(coder.age)
    coder.age = 18
    print(coder.age)
    print(f"{'=' * 10} 6、显式添加同名数据描述符age到__dict__ {'=' * 10}")
    coder.__dict__['age'] = 35
    print(coder.__dict__)
    print(coder.age)
    print(f"{'=' * 10} 7、定义__getattr__时访问不存在的属性 {'=' * 10}")
    Programmer.__getattr__ = my_getattr
    coder.skill
    print(f"{'=' * 10} 8、未定义__getattr__时访问不存在的属性 {'=' * 10}")
    del Programmer.__getattr__
    coder.skill

执行结果:

60731dbbd827cd33834d6c033e57fd0b.jpeg

总结

本文介绍了属性描述符的种类,并比较了不同的属性描述符在属性解析时的差异,最后结合属性描述符、__getattribute__()、__getattr__()及MRO等,给出了一个相对完整的属性解析顺序。

需要说明的是,属性描述符及后面的文章中要介绍的元类的概念,在通常意义的业务场景中是很少用到的。但是,如果涉及到框架的开发或者需要阅读框架的源码时,对这些内容的掌握还是很有必要的。

感谢您的拨冗阅读。如果对您学习Python有所帮助,欢迎点赞、收藏。

标签:__,Python,self,value,描述符,MRO,40,print,属性
From: https://blog.csdn.net/dqrcsc/article/details/141136306

相关文章

  • ABAP 7.40 快速参考-内联声明
     7.40之前7.40数据DATAtextTYPEstring.text='ABC'.DATA(text)='ABC'.循环进入工作区DATAwalikeLINEOFitab.LOOPATitabINTOwa....ENDLOOP.LOOPATitabINTODATA(wa)....ENDLOOP.调用方法DATAa1TYPE...D......
  • python之numpy (5 分割和复制)
    分割分割指将矩阵分割为几个小部分,以便于后续的计算需要。splitimportnumpyasnpm=np.random.random((3,3))print(m)sp=np.split(m,3,axis=0)ssp=np.split(m,3,axis=1)print(sp,ssp,sep='\n')[[0.373247510.933194940.18961048][0.814330810.377225750.00708......
  • python之numpy(4 选择数据及合并)
    选择数据importnumpyasnpm=np.random.random((3,3))print(m)print(m[0],m[1][1],sep='\n')print(m[1,1])print(m[1,:])print(m[:,1])结果:[[0.25960570.047399260.76332494][0.865032270.290489970.79591841][0.50535280.201822340.19601046]][......
  • windows保姆级的pycharm+anaconda搭建python虚拟环境
    (一)pycharm安装1.下载(1)从官网下载 ,一般来说选择社区版就够用了。我这里选择2024.1.6的windows版本OtherVersions-PyCharmGetpastreleasesandpreviousversionsofPyCharm.https://www.jetbrains.com/pycharm/download/other.html 2.安装(1)双击下载好的pycharm安......
  • PAT-1006 换个格式输出整数 python实现
    1.题目本题较为简单,只需要获取数字的各位数再分别按要求拼接到一起即可。2.代码如下  """输入:23423输出:BBSSS1234SS123"""#本题较为简单,只需要获取数字的各位数再分别按要求拼接到一起即可n=int(input())#获取输入的数字r......
  • python图片处理
    设置图片像素fromPILimportImagedefset_image(path,width=280):"""设置图片像素"""#打开图片image=Image.open(path)#原来大小original_width,original_height=image.size#设置新的图片大小new_width,new_height=w......
  • java语言,MySQL数据库;电影推荐网站 30760(免费领源码)计算机毕业设计项目推荐万套实战教
    摘 要随着互联网时代的到来,同时计算机网络技术高速发展,网络管理运用也变得越来越广泛。因此,建立一个B/S结构的电影推荐网站;电影推荐网站的管理工作系统化、规范化,也会提高平台形象,提高管理效率。本电影推荐网站是针对目前电影推荐网站的实际需求,从实际工作出发,对过去的电影......
  • java语言,MySQL数据库;基于Web的高校知识共享系统设计与实现 32050(免费领源码)计算机毕业
    摘 要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对高校知识共享系统等问题,对高校知识共享系统进行研究分析,然后开发设计出高校知识共享系统以......
  • java语言,MySQL数据库;23825基于java的员工考勤系统(免费领源码)计算机毕业设计项目推荐万
    摘 要由于数据库和数据仓库技术的快速发展,员工考勤系统建设越来越向模块化、智能化、自我服务和管理科学化的方向发展。考勤管理系统对处理对象和服务对象,自身的系统结构,处理能力,都将适应技术发展的要求发生重大的变化。员工考勤系统除了具有共享系统的全部功能以外,能通过......
  • Manim的一个用于数学动画的 Python 库中渲染代码的功能。
       Code 函数是Manim(一个强大的数学动画库)中的一个重要工具,旨在将代码片段以视觉化的方式呈现。在教育和演示场合中,向观众展示算法或代码逻辑时,清晰的视觉效果是必不可少的。通过 Code 函数,用户可以轻松地将特定编程语言的代码导入,并且自定义其外观,包括字体、颜色、背景......