一步步走进Python class...

【Python】Step Into Python Class

Before All

Python作为一门面向过程兼容面向对象的语言,在面向对象中,使用class关键字来申明一个类。

But,是不是应该深入考虑一下这个class的底层实现过程呢?(不考虑CPython,仅仅考虑Python这一层)

申明一个类

非常简单的申明方式:

class X:
    pass

这样就声明了一个X类。

如果我们实现Java中类似构造函数这样的功能呢?可以使用__init__这个魔术方法,这个方法在类创建之后会被调用(这里的创建到底指的是什么,后面会谈到)

class X:
    def __init__(self, arg1, arg2) -> None:
        self.arg1 = arg1
        self.arg2 = arg2

经典类与新式类

首先,先说结论——Python3中都是新式类,Python2中有新式类也有经典类

什么是新式类?

  • 继承自object的,都是新式类

什么是经典类?

  • 不继承object的,叫作经典类

为什么Python3中都是新式类?

  • 因为Python3中,申明一个class的时候,默认继承了object

为什么Python2中有新式类也有经典类?

  • 在Python2中,显式申明class X(object),那么这个类就是新式类
  • 如果没有显式申明,而是单纯的申明一个class X:pass,那么这个类就是经典类

Python 2.1之前,经典类是唯一可用的形式,在Python 2.2才引入了新式类。

继承

单继承

Python中想声明一个class继承了某个class,可以使用如下的方式:

class X(object):
    pass

如上的代码,X继承了object这个类,而在Python3中,如果没有显式申明继承object也没关系,默认是继承自object的。

但是在Python2中,如果不显式申明继承object,那么这个类就不能调用object中的一些属性和方法。

多继承

在Python中,可以多继承。这点是其他语言不兼容的(C++除外)。

很多官方的库其实都用到了多继承的特性,比如socketserver这个库

image-20230530101218007

可以看到ThreadingUDPServerThreadingTCPServer都继承了ThreadingMixIn这个类,然后继承自己的父类

多继承有好处也有坏处,好处是可以灵活运用,坏处就是如果随意使用,很难看懂代码而且会存在一些属性冲突。

Mixins

Mixins是一种规范,上述socketserver中的两个类就是使用了Mixins的规范:

  • 需要混入使用的特性类,写在前面,例如这个ThreadingMixIn,就是需要混入使用的线程类
  • 需要继承的父类放在最后,例如这个UDPServer,就是ThreadingUDPServer需要继承的父类
  • 所有需要插入的Mixin特性的类以MixIn结尾

Minins只是一种开发规范,而不是硬性规定~

菱形继承

由于Python的多继承特性,可以引入一种新的继承模式——菱形继承

什么是菱形继承?其实是多继承的一种特殊的形式。之所以叫作菱形继承是因为,形状和菱形类似,也可以叫作钻石继承

image-20230530102815001

转化为代码的形式:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

那么上述继承关系就会出现一种现象,D应该怎么样继承B、C的属性?

假设BC都具有同一个方法func1,那么D调用这个方法是怎么找的呢?

我们可以查看D.__mro__,这个元组,或者调用D.mro()这个方法获取一个列表

这里的mro是Method Resolution Order的缩写,也就是 方法解析顺序

无论是上述得到的元组还是列表,都存储了D这个类的方法解析顺序

print(D.mro())
print(D.__mro__)
'''
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
'''

所以,D去查找属性或者方法的时候,就是按照这个列表的顺序往后查找,最后找公共父类object,如果这个列表中的所有类都没有某个属性或者方法,那么就会报错。

至于这个mro列表是如何生成的,在不同的Python版本,有一定的差异:

  • 经典类使用DFS
  • Python 2.2 的新式类使用MRO
  • Python 2.3 的新式类使用C3算法,同时也是Python3唯一支持的算法

下面就详细说说各种版本的MRO方法!

经典类的DFS

经典类的MRO方法非常简单,就是从左向右的DFS

按照上述的图片,其遍历顺序是[D,B,A,C,A],考虑到重复遍历跳过,那么真实的顺序是[D,B,A,C]

但是,这样会存在什么样的问题呢?

image-20230530111838949

如果按照从左向右的DFS顺序来进行MRO,那么我在调用D.show()的时候,会发生什么?

  • 由于是从左往右,先去找B有没有show,发现B没有,就去找A
  • A直接找到了show()
  • 所以D的对象会调用A中的show()

但是我们想要的效果是D去调用C.show(),这种MRO方式,严重违背了这种特性!

新式类的BFS

Python2.2后,引入了新式类,针对新式类,有一种全新的MRO方法

  • 经典类仍然使用从左向右的DFS
  • 新式类使用从左想右的BFS

由于新式类都会继承object,所以之前的继承图变成了这样:

image-20230530114846869

从左向右的BFS算法的MRO顺序就是[D,B,C,A,object]

这样想要执行D.show()的时候,就可以正常继承调用C.show()

但是,BFS就没有特殊情况吗?还是有的!

image-20230530131330330

翻译成代码:

class B:
    pass

class C:
    pass

class E(B, C):
    pass

class F(C, B):
    pass

class G(E, F):
    pass
  • BFS(广度优先)的结果是[G, E, F, B, C, object]
  • 对于F,搜索顺序是[F, C , B, ojbect] (object最后查找的原则)
  • 对于G,搜索顺序是[G, E, F, B, C, object]
  • F中的查询顺序是CB ,而G中的查询顺序是BC
  • 上述的查询顺序违背了单调性原则,这个原则的意思是:
  • 如果一个类X从父类X1和X2中派生出来,在MRO中,如果X1早于X2,那么在X的任何子类中都应该保持这个次序。
  • 上述的F的子类G明显违背了这个原则!

由于上述的BFS算法仅仅针对新式类,对于经典类DFS算法仍然违背了之前的本地优先级原则

新式C3算法

Python 2.3之后采用了C3算法来处理MRO

C3算法解决了单调性问题只能继承无法重写问题,是基于拓扑排序的思想来解决问题的。

Python官方文档在2.3版本给出了相关的详细讲解The Python 2.3 Method Resolution Order | Python.org

image-20230530143239195

对于只能继承无法重写的问题,解决思路:

  • 造成这个的本直原因是——先查询了子类的父类,而另一个子类的方法重写没有生效,也就是DFS中存在的问题
  • 把继承关系链看作一张有向无环图,使用拓扑排序的方式,构建一个全序序列,保证子类一定优先于父类被搜索

对于上图的继承关系链,我们使用拓扑排序的方式来进行一次排序:

  • 首先找图中入度 == 0 的点,刚开始只有A,选择A
  • A的边删除,再找入度 == 0 的点,只有B、C
  • 按照最左原则,选择B
  • B的边删除,再找入度 == 0 的点,只有D、C
  • 按照最左原则,选择D
  • D的边删除,再找入度 == 0 的点,只有C
  • 选择C,删除C的边
  • 此时入度 == 0的点有E、F
  • 按照最左原则,选择E
  • E的边删除,再找入度 == 0 的点,只有F
  • 选择F
  • 最后选择object

所以最后的排序是[A, B, D, C, E, F, object]

C3算法就是在拓扑排序的基础上构建的,会对图中的每一个节点进行排序计算,一旦发现了逆序的存在就会产生一个TypeError

这也就从根源上禁止创建具有二义性的继承关系了

GraphError
image-20230530131330330Traceback (most recent call last):
File "e:/myworks/vscode_workspace/python_workspace/python_learning/class_learning.py", line 13, in
class G(E, F):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases B, C

如果我们从算法角度来考虑C3算法,其实就是把子类的MRO次序基于父类的MRO次序进行merge,并且在每次迭代的过程中,测试是否存在逆序的情况

其中merge方法定义为:

  • 检查第一个序列的头元素,记作 H。

  • 若 H 未出现在其它列表的尾部,则将其输出,并将其从所有列表中删除,然后回到步骤1;否则,取出下一个列表的头部记作 H,继续该步骤。(这个步骤,相当于拓扑排序中的查找并删除入度为0的节点。)

  • 重复上述步骤,直至列表为空或者 不能再找出可以输出的元素。如果是前一种情况,则算法结束;如果是后一种情况,说明无法构建继承关系(存在二义性继承),Python 会抛出异常。

我们来看不同的继承关系的C3算法的流程:

GraphC3 Code
image-20230530143239195mro(D) = [D,O]
mro(E) = [E,O]
mro(F) = [F,O]
mro(B) = [B] + merge(mro(D),mro(E),[D,E])
= [B] + merge([D,O],[E,O],[D,E]) # E符合merge条件
= [B,D] + merge([O],[E,O],[D]) # D符合merge条件
= [B,D,E] + merge([O],[O],[]) # O符合merge条件
= [B,D,E,O]
mro(C) = [C] + merge(mro(E),mro(F),[E,F])
= [C] + merge([E,O],[F,O],[E,F]) # E符合merge条件
= [C,E] + merge([O],[F,O],[F]) # F符合merge条件
= [C,E,F] + merge([O],[O],[]) # O符合merge条件
= [C,E,F,O]
mro(A) = [A] + merge(mro(B),mro(C),[B,C])
= [A] + merge([B,D,E,O] ,[C,E,F,O] ,[B,C]) # B符合merge条件
= [A,B] + merge([D,E,O] ,[C,E,F,O] ,[C]) # D符合merge条件
= [A,B,D] + merge([E,O] ,[C,E,F,O] ,[C]) # C符合merge条件
= [A,B,D,C] + merge([E,O] ,[E,F,O] ,[]) # E符合merge条件
= [A,B,D,C,E] + merge([O] ,[F,O] ,[]) # F符合merge条件
= [A,B,D,C,E,F] + merge([O] ,[O] ,[]) # O符合merge条件
= [A,B,D,C,E,F,O]
image-20230530131330330mro(B) = [B, O]
mro(C) = [C, O]
mro(E) = [E] + merge(mro(B), mro(C), [B, C])
= [E] + merge([B, O], [C, O], [B, C])
= [E, B] + merge([O], [C, O], [C])
= [E, B, C] + merge([O], [O], [])
= [E, B, C, O]
mro(F) = [F] + merge(mro(C), mro(B), [C, B])
= [F] + merge([C, O], [B, O], [C, B])
= [F, C] + merge([O], [B, O], [B])
= [F, C, B] + merge([O], [O], [])
= [F, C, B, O]
mro(G) = [G] + merge(mro(E), mro(F), [E, F])
= [G] + merge([E, B, C, O], [F, C, B, O], [E, F])
= [G, E] + merge([B, C, O], [F, C, B, O], [F])
= [G, E, F] + merge([B, C, O], [C, B, O], []) # 矛盾出现

所以在Python3中,就是使用C3算法来进行MRO搜索的,确保了二义性的多继承不会出现!

super的细节

super只能在新式类中使用!

在一个class中,可以使用super()来获取到这个类在mro列表中的下一个类

那你可能就会说了,super肯定是调用父类的属性或者方法!

但是,这是错误的!

举一个例子!

class A:
    def f1(self):
        print("A f1")
        super().f1()

class B:
    def f1(self):
        print("B f1")

class C(A, B):
    pass

C().f1()

上述的代码会打印什么呢?答案是:

  • A f1

  • B f1

明明A和B没有继承关系,为什么A中的super().f1()会执行B中的f1呢?

其实是因为super看的是最初对象的mro列表

C的mro是,[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

所以在A中的super()获取到的是mro列表中<class '__main__.A'>的下一个,也就是 <class '__main__.B'>

Duck Typing

duck typing就是我们常说的鸭子类型

在Python中,如果某个类实现了一些特定的方法,但是没有显式继承某个类,这个类也可以是某些类的子类!

from collections.abc import Iterable

class MyIter:
    def __iter__(self):
        pass

print(issubclass(MyIter, Iterable))	# True

虽然这个MyIter没有明确写上继承自Iterable,但是通过issubclass的信息可以知道,MyIter就是Iterable的一个子类

这就是鸭子类型,如果一个东西,它长得像鸭子,叫声像鸭子,走路像鸭子,那么它就是鸭子!

而鸭子类型的实现,可以通过__subclasshook__这个魔术方法来实现!

我们来看看Iterable的源码:

class Iterable(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            return _check_methods(C, "__iter__")
        return NotImplemented
  • Iterable定义自己的元类ABCMeta,有关元类的知识点之后再讨论。
  • Iterable定义了一个abstractmethod,这个抽象方法装饰器的作用就是,如果一个类显式继承Iterable,那么这个类一定要有一个__iter__的方法,否则会报错
  • Iterable定义了一个classmethod叫作__subclasshook__,这个方法在调用issubclass()函数的时候会被调用,检测C这个是否含有__iter__这方法,如果有,就返回True,那么在调用issubclassisinstance的时候就会返回True,证明是子类

说到了这里,其实Python中很多类型都实现了__subclasshook__这个方法,例如上面提到的可迭代对象Iterable,还有迭代器Iterator,还有生成器Generator,都是通过判断某个类中是否存在某些函数来判断是否是子类。

其实可以自己编写一个demo:

from abc import ABC

class MyBaseClass(ABC):
    @classmethod
    def __subclasshook__(cls, subclass):
        print(cls)
        print(subclass)
        return hasattr(subclass, 'my_method') and callable(subclass.my_method)

class MySubClass:
    def my_method(self):
        print("Hello, World!")
        

print(issubclass(MySubClass, MyBaseClass)) 	# True
print(isinstance(MySubClass(), MyBaseClass))# True

抽象基类

如果不想使用Duck Typing的方式来实现多态,可以使用抽象基类的方式来规范父子类标准。

从一个小demo看起:

from abc import ABC, abstractmethod

class MyBaseClass(ABC):
    @abstractmethod
    def need_this_func(self):
        pass
    
    @classmethod
    def __subclasshook__(cls, subclass):
        print(cls)
        print(subclass)
        return hasattr(subclass, 'my_method') and callable(subclass.my_method)

class MySubClass(MyBaseClass):
    def my_method(self):
        print("Hello, World!")
        
MySubClass()
'''
Traceback (most recent call last):
  File "e:/myworks/vscode_workspace/python_workspace/python_learning/class_learning.py", line 88, in <module>
    MySubClass()
TypeError: Can't instantiate abstract class MySubClass with abstract methods need_this_func
'''

上述代码实现了一个抽象基类MyBaseClass,因为这个类继承了abc.ABC,而abc.ABC的元类是abc.ABCMeta,这个元类规范了使用了abstractmethod修饰的函数一定要在子类中实现,否则报错

所以在我们自己写的MySubClass中,显式继承MyBaseClass,如果没有实现need_this_func,那么就会报错!

使用抽象基类的方式可以更好的规范我们的父子类代码!

类方法与静态方法

类方法在class中可以使用装饰器@classmethod来修饰,第一个参数是cls,表示当前这个类

class X:
    @classmethod
    def instance(cls):
        if hasattr(cls, "_obj"):
            return cls._obj
        cls._obj = X()
        return cls._obj
    
print(X.instance())
print(dir(X))
'''
<__main__.X object at 0x000001EB271CFD30>
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_obj', 'instance']
'''

这里就用类方法类生成了一个实例,同时这也是单例模式的一种写法

静态方法可以看作类方法少了cls这个参数,就相当于是绑定到一个类上的一个方法,不需要生成实例就可以调用的方法

class Util:
    @staticmethod
    def date():
        return "20230531"
    
print(Util.date())	# "20230531"

metaclass

metaclass翻译成中文,就是元类

我们都知道,在Python中,万物皆对象,但是,思考一个问题——对象是由类创建的,那么类是由什么创建的呢?

答案是,类是由其元类创建的,同时元类也是一种类

type元类

在Python的基础类型中,所有基础类元类type

你可能会疑惑了,type不是用来查看某个对象的类的吗?

但是type的用法远远不止这一条!

我们先来证明一下基本类型都是由元类构建的,看如下的代码输出!

print(type(str(1)))
print(type(str))
print(type(int("1")))
print(type(int))
print(type(list("123")))
print(type(list))
print(type(tuple("123")))
print(type(tuple))
'''
<class 'str'>
<class 'type'>
<class 'int'>
<class 'type'>
<class 'list'>
<class 'type'>
<class 'tuple'>
<class 'type'>
'''

发现了没有:

  • 所有由基本类型构建的对象执行type()后,都指向这个类
  • 基本类型的type()执行后,返回的都是<class 'type'>
  • 这个<class 'type'>其实就是metaclass,也就是元类

来解释一下相关概念:

  • 元类:实例化产生类的类
  • 元类 --> 元类.实例化() --> 类 --> 类.实例化() --> 对象

既然我们知道了基本的数据类型都是由type这个类构建而来的,那么我们可以不使用class这种关键字创建出一个吗?

答案是——肯定可以!

我们就使用最基本的metaclass——type,来实现无class关键字构造一个

class_name = "Wood"     # 类名
class_bases = (object, )# 基类
class_dic = {}
class_body = '''
def __init__(self):
    pass
def info(self):
    print("test info")
'''
exec(class_body, {}, class_dic) # 使用exec将class_body中的内容装入class_dic中
Wood = type(class_name, class_bases, class_dic)
obj = Wood()
obj.info()
print(dir(obj))
print(type(obj))

'''
test info
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'info']
<class '__main__.Wood'>
'''

上述代码的输出结果已经告诉我们了,这个Wood类已经被创建了,并且是使用type的方式构建的!

我们可以根据上述的代码思考一些问题:

  • class关键字到底做了什么来构建了一个class?
  • 元类充当了什么样的一个角色?

其实第一个问题思考一下就可以得出简单的答案:

  • 获取类名
  • 获取基类(父类)
  • 获取名称空间
  • 调用元类构建这个类

第二个问题在刚开始就说了:

  • 元类的实例化对象是一个

自定义元类

先看一个demo,继承了type的元类

class MyMetaclass(type):
    def __init__(self, *args, **kwargs):
        print(args)
        print(kwargs)
        print(self)
        print(self.mro())
        print(dir(self))
        


class X(metaclass=MyMetaclass):
    def func1(self):
        pass

X()
'''
('X', (), {'__module__': '__main__', '__qualname__': 'X', 'func1': <function X.func1 at 0x000001D969F9C1F0>})
{}
<class '__main__.X'>
[<class '__main__.X'>, <class 'object'>]
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'func1']
'''

可以看到输出,args中有三个参数:

  1. 类名
  2. 基类(父类)
  3. 命名空间

所以可以看到,在元类__init__参数中,传入了需要被构造的类的必要属性

那么我们可以在元类中执行什么样的操作呢?同时又有如下几个疑点:

  • 如果仅仅是在__init__中注入一些属性,那么如何解释传入的参数中不存在object,但是self.mro()中又有object了?
  • Python3中都是新生类,如何保证class申明后不显式继承object也会自动继承object
  • 有什么方法是在__init__之前就执行的?

我们一个一个来解答,首先来了解一个类的构建过程。

__new__

我们一般会把一个class__init__函数当作构造函数。

但是,其实真正创建一个类的函数是__new__

我们来看一个例子,证明__new____init__之前执行

class MyMetaclass(type):
    def __init__(self, *args, **kwargs):
        print("-"*50)
        print(args)
        print(kwargs)
        print(self)
        print(self.mro())
        print(dir(self))
        
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)
        return super().__new__(cls, *args, **kwargs)


class X(metaclass=MyMetaclass):
    def func1(self):
        pass

x = X()

我们直到metaclass构建一个类的流程,由metaclass创造一个类对象,上述代码中这个类对象就是X,再由X创造一个类对象,这个就是上述代码中的x

那么上述代码的输出是什么呢?

<class '__main__.MyMetaclass'>
('X', (), {'__module__': '__main__', '__qualname__': 'X', 'func1': <function X.func1 at 0x0000013C3A89C280>})
{}
--------------------------------------------------
('X', (), {'__module__': '__main__', '__qualname__': 'X', 'func1': <function X.func1 at 0x0000013C3A89C280>})
{}
<class '__main__.X'>
[<class '__main__.X'>, <class 'object'>]
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'func1']

可以看到,__new__的输出早于__init__,所以我们的结论被证实了。

  • 每个class,都是由__new__构造出来的
  • __new__早于__init__

那么在哪个地方会让metaclass去调用__new__再去调用__init__,最终再去构造一个基本类呢?

  • 有一个魔术方法叫作__call__,就是一个类的对象在被调用的时候会执行的

__call__

如果在一个类中,指定了__call__方法,那么这个类的实例化对象也可以加上()被调用

class TestClass:
    def __init__(self, name) -> None:
        self.name = name
    
    def __call__(self, *args, **kwds):
        print(self.name)

obj = TestClass("woodwhale")
obj()	# woodwhale

上述代码中实例化的对象obj,如果被当作函数执行,就会触发__call__函数,从而执行print(self.name)这条语句

  • __call__函数是可以有返回值的,当作一个函数就好理解了
  • __call__函数还可以有参数,这点也和函数一样

还记得如何使用type来构造一个class

class_name = "TestClass"
class_bases = (object, )
class_dic = {}
class_body = '''
def __init__(self):
    pass
def info(self):
    print("test info")
'''
exec(class_body, {}, class_dic) 
TestClass = type(class_name, class_bases, class_dic)
  • 既然type是一个类,那么在调用type()的时候,其实触发的就是type这个类的__call__方法
  • 也就是说,在执行type(class_name, class_bases, class_dic)的时候,其实是在__call__函数中返回了一个类对象

让我们思考一个问题,一个最简单的,它的__init__函数,为什么一定会被调用?

  • 其实就是因为最基本的类的元类是type,而在type__call__函数中,会调用这个最基本类的__new____init__

那么如果我们编写一个MyType继承自type,同时在这个MyType__call__中动手脚,是不是可以控制一个类的产生?

class MyMetaclass(type):

    def __call__(self, *args, **kwds):
        print(self)
        print("__call__")
        return "woodwhale"


class X(metaclass=MyMetaclass):
    def func1(self):
        pass
print(X())
'''
<class '__main__.X'>
__call__
woodwhale
'''

可以看到,这里X()的返回值是woodwhale,调用链如下:

  1. X()的第一步会去先实例化一个MyMetaclass
  2. MyMetaclass的实例化对象是X
  3. X()其实就是MyMetaclass实例化对象被调用了,所以会触发MyMetaclass__call__函数
  4. MyMetaclass__call__中,返回值是woodwhale
  5. 所以最后print(X())的结果是woodwhale

这样的调用链就很清晰了!

那么我们可以在__call__中,实例化一个X的对象

class MyMetaclass(type):

    def __call__(self, *args, **kwargs):
        print(self)
        print("__call__")
        x_obj = self.__new__(self)
        self.__init__(self, *args, **kwargs)
        return x_obj


class X(metaclass=MyMetaclass):
    
    def __new__(cls, *args, **kwargs):
        # return object.__new__(cls)
        obj = super().__new__(cls)
        # 随意操作!
        return obj
    
    def __init__(self, name) -> None:
        print("__init__")
        self.name = name

    
    def func1(self):
        pass
x = X("123")
print(x.name)

所以,如果我们想给一些类做定制化处理,可以在metaclass__call__函数中处理

属性查找

在通常的认知中,类与对象的属性查找的链子是:

  • 对象 --> 类 --> 父类 --> object
  • 找不到就报错

但是,在了解了metaclass这样的存在之后,其实查找链就有两种情况:

  1. 对于对象
  2. 对于类

对于对象的查找顺序,其实还是我们认知中的链子:

  • 对象 --> 类 --> 父类 --> object
  • 找不到就报错

但是,对于类,就不同了,多了一个查找元类的链子:

  • 对象 --> 类 --> 父类 --> object --> 自定义的metaclass --> type
  • 找不到就报错

After All

上述内容仅仅针对Python代码层面的类,并没有分析CPython这种更深层次的源码分析,未来有时间一定看源码!