【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
这个库
可以看到ThreadingUDPServer
和ThreadingTCPServer
都继承了ThreadingMixIn
这个类,然后继承自己的父类
多继承
有好处也有坏处,好处是可以灵活运用,坏处就是如果随意使用,很难看懂代码而且会存在一些属性冲突。
Mixins
Mixins是一种规范,上述socketserver
中的两个类就是使用了Mixins
的规范:
- 需要混入使用的特性类,写在前面,例如这个
ThreadingMixIn
,就是需要混入使用的线程类
- 需要继承的父类放在最后,例如这个
UDPServer
,就是ThreadingUDPServer
需要继承的父类 - 所有需要插入的Mixin特性的类以
MixIn
结尾
Minins只是一种开发规范,而不是硬性规定~
菱形继承
由于Python的多继承特性,可以引入一种新的继承模式——菱形继承
什么是菱形继承?其实是多继承的一种特殊的形式。之所以叫作菱形继承
是因为,形状和菱形
类似,也可以叫作钻石继承
转化为代码的形式:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
那么上述继承关系就会出现一种现象,D
应该怎么样继承B、C
的属性?
假设B
和C
都具有同一个方法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]
但是,这样会存在什么样的问题呢?
如果按照从左向右的DFS
顺序来进行MRO,那么我在调用D.show()
的时候,会发生什么?
- 由于是从左往右,先去找
B
有没有show
,发现B没有,就去找A
- 在
A
直接找到了show()
- 所以
D
的对象会调用A
中的show()
但是我们想要的效果是D去调用C.show()
,这种MRO方式,严重违背了这种特性!
新式类的BFS
Python2.2后,引入了新式类
,针对新式类,有一种全新的MRO方法
经典类
仍然使用从左向右的DFS
新式类
使用从左想右的BFS
由于新式类都会继承object
,所以之前的继承图变成了这样:
从左向右的BFS
算法的MRO顺序就是[D,B,C,A,object]
这样想要执行D.show()
的时候,就可以正常继承调用C.show()
了
但是,BFS就没有特殊情况
吗?还是有的!
翻译成代码:
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
对于只能继承无法重写
的问题,解决思路:
- 造成这个的本直原因是——先查询了子类的父类,而另一个子类的方法重写没有生效,也就是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
这也就从根源上禁止创建具有二义性的继承关系了
Graph | Error |
---|---|
Traceback (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算法的流程:
Graph | C3 Code |
---|---|
mro(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] + 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©,[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] | |
mro(B) = [B, O] mro© = [C, O] mro(E) = [E] + merge(mro(B), mro©, [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©, 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,那么在调用issubclass
和isinstance
的时候就会返回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
中有三个参数:
- 类名
- 基类(父类)
- 命名空间
所以可以看到,在元类
的__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
,调用链如下:
X()
的第一步会去先实例化一个MyMetaclass
MyMetaclass
的实例化对象是X
- 而
X()
其实就是MyMetaclass
实例化对象被调用了,所以会触发MyMetaclass
的__call__
函数 - 在
MyMetaclass
的__call__
中,返回值是woodwhale
- 所以最后
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
这样的存在之后,其实查找链就有两种情况:
- 对于对象
- 对于类
对于对象的查找顺序,其实还是我们认知中的链子:
- 对象 --> 类 --> 父类 --> object
- 找不到就报错
但是,对于类,就不同了,多了一个查找元类的链子:
- 对象 --> 类 --> 父类 --> object --> 自定义的metaclass --> type
- 找不到就报错
After All
上述内容仅仅针对Python
代码层面的类,并没有分析CPython
这种更深层次的源码分析,未来有时间一定看源码!