python中类的基本结构、特殊属性于MRO理解

上次了解了__getattribute__和__getattr__,了解它的过程中,因为__getattributr__的内置函数的逻辑是: 当访问 obj.attr 时,Python 底层的 object.__getattribute__ 会这样查找:

  1. 先查实例字典 obj.__dict__ (对象自己的属性)
  2. 如果没找到,按 MRO 顺序 依次查每个类的 __dict__
  3. 如果还是没找到,调用 __getattr__(如果类里定义了)
  4. 最后找不到就抛 AttributeError

于是接触到了两个概念,一个是__dict__,第二个是MRO,所以今天又来继续总结这两个概念。在总结这两个概念前,我需要先总结一下python中的一些关键分类。

内置函数、特殊属性、魔术方法

在Python中内置了很多东西,其中和__getattr_getattr__module__ _dict__等等这些有关的分类,我暂时把他们归类为内置函数、类中内置方法、类中特殊属性。python当然还有其他的很多内置的东西,这里先暂时不讨论。

  • 内置函数,比如getattr就是内置函数,是python已经写好的,只要运行了python就可以随意使用,导出都可以用,不需要在文件中导入什么东西。python共有71个内置函数,可以看这里python中71个内置函数
  • 类中内置方法,类中内置方法就是object中已经默认定义好的一些方法。除了object类中的一些默认方法以外,一些内置的类,比如listdictstr也内置了很多的方法。
  • 类中特殊属性:像__module____dict__就是一些在类中的特殊属性,这两个属性是在python的type类构建每个类时默认给加上的属性,是属于类的元数据,也就是说每个类都有这些属性。

上面并没有说到像__len____getattr__这样的魔术方法。这里只单独举例说一下魔术方法和内置函数的关系。比如__len__是一个魔术方法,这个魔术方法在object中没有定义,而是在object的子类中,比如list中由python默认去定义了。其实可以说这个魔术方法的内容是人为进行定义的,只是方法名称是固定的。如果一个类中定义了__len__方法,那么就以通过内置的函数len()去调用类中的这个__len__方法。这也是内置函数和一些魔术方法相联系的地方,通过这样一些python中的默认的协议来进行关联调用。

__dict__

__dict__是一个特殊属性,它是一个存在普遍存在于类、对象实例中的一个特殊属性,是type类在构建类的时候给加上去的。

说到这里,就不得不先理解一下type构建类的过程。我最初使用type是用来查看对象的类型,比如type(5),得到的是<class int>,而如果type(int),就会得到<class 'type'>,所以我先来了解一下type

type是python中的元类,是所有类的创建者,注意不是继承,而是创建。所有类都是继承自object,而所有类都是被type创建的。我们在使用关键字class定义类的时候,实际上就是type在工作。

python 复制代码
class A:
	stark = '123'
	
	def get_stark(self):
		return 'stark'

当我们像上面这样定义一个类的时候,实际上python就在用type做下面四个步骤:

  1. 确定元类、类名和它的基类。实际上class A: 等同于class A(classmeta=type):所以这一步就是在确认,正在定义的这个类,它的classmeta是什么。类名就是A,基类就是object,元类就是type
  2. 为存储类的属性准备一块存储区域,也叫做准备类的命名空间。这个存储一般用字典(dict)来存储。
  3. 执行类的主体代码,就是把class A:中的主体代码用python解释器读一遍,把独到的属性、方法都写到第2步准备的存储空间里面去,给这些键值对放到一个叫做attrs的字典中。注意__module____dict__这类type构建类的时候写入的类的元数据,就是在这一步写入的。
  4. 创建类,就是调用type里面的一个方法__new__把第一步中的确认的类名、基类和第三步中读完类主体代码后得到的一个attrs的字字典一起传给__new__,就完成了类的创建的了。

所以上面的class A:的定义约等于python干了下面这个

python 复制代码
A = type(
	'A',                                  # 类名
	(object,),                            # 基类 bases
	{
		'__module':'__main__',
		'__dict__':<attribute '__dict__' of 'A' objects>,
		'stark':123,
		'get_stark':<function A.speak>,   # attrs,存储类中全部的成员键值对
		'__doc__': None
	}
)

所以在类中的__dict__实际上就是第3步执行完类的主体代码后得到的attrs,两个的核心几乎是一样的。只不过attrs得到的是一个临时的,而__dict__得到的是这个临时的attrs传给type后最后构建出来实际的,经过type构建后的__dict__会比attrs多一些内容,多的这些就是type构建给加上的,比如说__dict__

而在对象中的__dict__实际上就只是对象实例中具体成员了,不再有__module____dict__这类元数据了。比如下面这个:

python 复制代码
class B:
    def __init__(self, name, age):
        self.name = name
        self.age =age

    def __len__(self):
        return '42'

b = B('Bob', 10)

print(B.__dict__)
# --> {
# -->   '__module__': '__main__',
# -->   '__init__': <function B.__init__ at 0x0000019ACE004A40>,
# -->   '__len__': <function B.__len__ at 0x0000019ACE004B80>,
# -->   '__dict__': <attribute '__dict__' of 'B' objects>,
# -->   '__weakref__': <attribute '__weakref__' of 'B' objects>,
# -->   '__doc__': None
# --> }

print(b.__dict__)
# --> {'name': 'Bob', 'age': 10}

__class__

上面提到的是type构建类的用法,但我们最常用的是type(5)这种用法。

实际上这个type(5)只是在调用类的__class__属性,这个__class__属性存储的就是对象的类型,它是对象一个特殊属性。

而这个__class__就是type构建的类的基本结构中的特殊属性,属于类的基本结构,它这个属性想要表达的是这个对象是由哪个类创建的。所以才有

python 复制代码
class A:
	pass
	
a = A()

print(type(a))
# --> <class '__main__.A'>

print(type(A))
# --> <class 'type'>

MRO

MRO(method Resolution Order)方法解析顺序,用来指明python中类在继承的过程中,如何找到某个对象的方法是继承自哪一个类。实际上,它也是对象的一个特殊属性__mro__,而这个__mro____class__一样也是在type构建类时就创建好的特殊属性。

在类的继承过程中,__mro__中的值是通过一个叫做C3线性化的算法算出来的元组,当在对象中去调用super().XX方法的时候,就是去__mro__中去找到下一个对象。

和这个文章开头提到的对象的属性查找过程的MRO顺序,实际上就是用到了__mro__:

  1. 先查实例的__dict__是否有这个属性
  2. 如果没找到,就按照__mro__中的顺序依次去查询每个类的__dict__
  3. 如果一直没找到,就去调用__getattr__,如果类中定义了这个__getattr__的话
  4. 如果都没有,就抛出AttributeError

那么这个C3线性化算法大概是什么样一个算法呢?举个例子来尝试理解一下:

python 复制代码
class O: pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(A, B): pass
class E(B, C): pass
class F(D, E, C): pass

print(F.__mro__)

要计算F.__mro__可以分为下面几个步骤来理解:

  1. 整理继承关系
  1. 找出父类的MRO
python 复制代码
MRO(O) = O
MRO(A) = [A, MRO(O)] = [A, O]
MRO(B) = [B, MRO(O)] = [B, O]
MRO(C) = [C, MRO(O)] = [C, O]
MRO(D) = [D] + merge(MRO(A), MRO(B), [A, B]) = [D] + merge([A, O], [B,O], [A, B]) = [D, A, B, O]
MRO(E) = [E] + merge(MRO(B), MRO(C), [B, C]) = [E] + merge([B, O], [C,O], [B, C]) = [E, B, C, O] 
MRO(F) = [F] + merge(MRO(D), MRO(E), MRO(C), [D, E, C]) = [F] + merge([D, A, B, O], [E, B, C, O], [C, O], [C, D, E]) 
  1. 列出多个列表,把这些列表合并成一个线性序列,同时保持相对顺序
python 复制代码
MRO(F) = [
	[F]
	[D, A, B, O]
	[E, B, C, O]
	[C, O]
	[D, E, C]
]
  1. 合并操作,合并操作就是从第一个列表的第一个元素开始看。、

    1. 首先看F元素,F没有在剩下的任何一个列表的非队首,那么就把F放到MRO(F)元组的第一个中去,去掉所有列表中的F
    2. 第一个列表已经没有,就看第二个列表的第一个元素D,D也不在剩下的任何一个列表中非队首,所有就放到MRO(F)中第二个去,去掉所有列表中的D
    3. 再看第二个列表的第二个元素A,同样不在剩下的任何一个列表的非队首 ,放到MRO(F)中第三个去,去掉所有列表中的A
    4. 再看第二个列表中第三个元素B,B在第第三个列表中排在第2位,所以不选B
    5. 再看第三个列表中的第一个E,E已经不在任何一个列表的非队首了,所有第四个元素选E,去除所有列表中的E
    6. 再看第二个列表中的B,B也不已经不再任何一个列表的非队首了,所以第五个元素选D
    7. 再看第三个列表中的C,C也已经不再任何一个列表的非队首了,所以第六个元素选C
    8. 现在只剩O了,所有第六个元素选O
    9. MRO(F)就是(F, D, A, E, B, C, O)
  2. 所有F.__mro__打印出来就是

python 复制代码
(
    <class '__main__.F'>,
    <class '__main__.D'>,
    <class '__main__.A'>,
    <class '__main__.E'>,
    <class '__main__.B'>,
    <class '__main__.C'>,
    <class '__main__.O'>,
    <class 'object'>
)

注意:在定义类的时候,如果继承了多个类,那么在定义的时候父类的写的顺序,就是计算MRO的要写的顺序。

python中类的数据结构

实际上上面我们所说的都是python中的类的基本的数据结构,也就是说任何一个类在电脑中存储,都是按照下面的结构来存储的。

python 复制代码
ClassObject (type instance)
├─ __dict__     → 用户定义的属性/方法
├─ __mro__      → 方法解析顺序
├─ __bases__    → 父类元组
├─ __class__    → type
├─ __weakref__  → 支持弱引用
└─ __module__, __doc__, __qualname__ → 其它元信息

上面我们也只是看了__dict____mro____class__三个类的基本机构中的特殊属性。

最后再提一下__base____bases__这两个特殊属性,__bases__是取对象的全部直接基类 ,形成一个元组,而__base__是对象的第一个父类,类似于是__bases__[0]

python 复制代码
class O: pass
class A(O): pass
class D(A, O): pass

print(O.__base__)
# --> <class 'object'>

print(A.__base__)
# --> <class '__main__.O'>

print(D.__base__)
print(D.__bases__)
# --> <class '__main__.A'>
# --> (<class '__main__.A'>, <class '__main__.O'>)
相关推荐
迈火1 小时前
PuLID_ComfyUI:ComfyUI中的图像生成强化插件
开发语言·人工智能·python·深度学习·计算机视觉·stable diffusion·语音识别
浔川python社3 小时前
《网络爬虫技术规范与应用指南系列》(xc—5)完
爬虫·python
MongoVIP4 小时前
Scrapy爬虫实战:正则高效解析豆瓣电影
python·scrapy
李小白664 小时前
Python文件操作
开发语言·python
weixin_525936335 小时前
金融大数据处理与分析
hadoop·python·hdfs·金融·数据分析·spark·matplotlib
Zwb2997925 小时前
Day 30 - 错误、异常与 JSON 数据 - Python学习笔记
笔记·python·学习·json
码界筑梦坊6 小时前
206-基于深度学习的胸部CT肺癌诊断项目的设计与实现
人工智能·python·深度学习·flask·毕业设计
flashlight_hi6 小时前
LeetCode 分类刷题:74. 搜索二维矩阵
python·算法·leetcode·矩阵
通往曙光的路上6 小时前
国庆回来的css
人工智能·python·tensorflow
不语n7 小时前
Windows+Docker+AI开发板打造智能终端助手
python·docker·树莓派·香橙派·dify·ollama·ai开发板