【Python】Step Into Python Class

news2024/11/16 15:29:46

【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] + 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]
image-20230530131330330mro(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,那么在调用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这种更深层次的源码分析,未来有时间一定看源码!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/612095.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

msvcp110.dll丢失怎么修复(一键修复办法)

msvcp110.dll是C编程中非常重要的库文件之一。它实现了运行时库的大部分功能&#xff0c;并提供了许多标准库和其他功能的具体实现&#xff0c;如多线程编程和IO操作等。提高程序的运行效率和稳定性。下面是详细解决msvcp110.dll丢失问题的方法跟msvcp110.dll文件的介绍。 msvc…

npm install报错 -> npm ERR! Unexpected token ‘.‘ 报错解决办法

原因&#xff1a; 我遇到这个问题的场景是用nvm1.1.7的版本安装了16.x以上的node, 然后再下载依赖的时候就报错了 总结一下就是nvm版本太低了&#xff0c;他的里面没有集成高版本node导致的 解决&#xff1a; 我们把nvm版本换到最新的就可以了 1. 卸载掉当前所有的node nvm …

ABIDE Preprocessed 结构态MRI数据集介绍及下载

ABIDE数据集介绍及下载 ABIDE Prerocessed项目是在ABIDE I 项目的基础上发展而来&#xff0c;主要是对ABIDE I中采集到的原始数据进行了一定的预处理和初步的特征提取。针对于fMRI和sMRI数据有着不同的处理方式&#xff0c;本次主要对其中提供的sMRI预处理结果进行介绍&#xf…

Python程序设计基础:标识符、变量与赋值、输入输出

文章目录 一、标识符二、变量与赋值三、输入输出 一、标识符 Python对每个标识符的命名存在要求&#xff1a; 1、每个标识符必须以字母或下划线“_”开头&#xff0c;后跟字母、数字或下划线的任意序列。根据这个规则&#xff0c;以下都是Python中的合法名称&#xff1a;a&…

excel如何实现识别文本在对应单元格填上数据?

要实现 Excel 识别文本在对应单元格填上数据&#xff0c;有以下两种方法&#xff1a; 方法一&#xff1a;使用 VLOOKUP 函数 1. 在 Excel 工作表中&#xff0c;输入一个表格&#xff0c;列名为对应的文本&#xff0c;行名为不同条目。 2. 准备输入数据&#xff0c;在一个新的…

python使用requests+excel进行接口自动化测试

在当今的互联网时代中&#xff0c;接口自动化测试越来越成为软件测试的重要组成部分。Python是一种简单易学&#xff0c;高效且可扩展的语言&#xff0c;自然而然地成为了开发人员的首选开发语言。而requests和xlwt这两个常用的Python标准库&#xff0c;能够帮助我们轻松地开发…

LInux之find查找

目录 LInux之find查找 定义 详解 格式 参数及作用 详解 1.按照文件名搜索 2.按照文件大小搜索 3.按照修改时间搜索 4.按照权限搜索 5.按照所有者和所属组搜索 6.按照文件类型搜索 7.逻辑运算符 8.其他选项 -exec参数 获取到该目录中所有以host开头的文件列表 如在…

【测试入门】测试用例经典设计方法 —— 因果图法

01、因果图设计测试用例的步骤 1、分析需求 阅读需求文档&#xff0c;如果User Case很复杂&#xff0c;尽量将它分解成若干个简单的部分。这样做的好处是&#xff0c;不必在一次处理过程中考虑所有的原因。没有固定的流程说明究竟分解到何种程度才算简单&#xff0c;需要测试…

3D打印机分类汇总

1 根据市场定位分类 当今市面上应用比较多的3D打印机是SLS、SLA、DLP、FDM四种3D打印机&#xff0c;按照用途可分为两类&#xff1a;一类是高精度工业打印机&#xff0c;比如SLA、DLP、SLS&#xff1b;一类是以FDM、SLA&#xff08;用于工业打印机更多&#xff09;为主的桌面级…

NRF52832空中升级DFU

1.工具环境搭建 gcc-arm-none-eabi编译环境&#xff1a;GCC编译环境 Downloads | GNU Arm Embedded Toolchain Downloads – Arm Developer mingw 平台&#xff08;win版的Linux命令行&#xff09; Download MinGW - Minimalist GNU for Windows micro-ecc-master源码 GitHu…

永不磨灭的设计模式(23种设计模式全集)

永不磨灭的设计模式 概述七大基本原则23种设计模式1、单例模式2、工厂(方法)模式3、抽象工厂模式4、原型模式5、建造者模式6、适配器模式7、桥接模式8、组合模式9、装饰器模式10、外观模式11、享元模式12、代理模式13、责任链模式14、命令模式15、迭代器模式16、中介者模式17、…

tinkerCAD案例:3.基本按钮

基本按钮 在本课中&#xff0c;您将学习制作具有圆柱形状的基本按钮。 说明 将圆柱体拖动到工作平面。 将其缩小到 2 毫米的高度。 提示&#xff1a; 您可以使用圆柱形状顶部的白点缩小圆柱体。 将其缩小到直径 16 毫米。 这将是按钮的主要形状。 现在我们可以创建允许将纽…

CSS动画:多动画同步播放或非同步播放

前言 本篇在讲什么 在CSS样式表现动画的基础上的拓展 本篇适合什么 适合初学H5的小白 适合初学CSS的小白 适合入门的前端程序 本篇需要什么 对Html和css语法有简单认知 Node.js(博主v18.13.0)的开发环境 Npm(博主v8.19.3)的开发环境 依赖VS code编辑器 本篇的特色…

impala内存超限

目录 一、背景 二、报错内容 三、解决办法 1.调参 2.简单粗暴 一、背景 impala shell执行SQL语句时报错 二、报错内容 Memory limit exceeded: Could not allocate memory while trying to increase reservation. 三、解决办法 1.调参 mem_limit参数&#xff1a;&…

ciscn 2023 初赛 pwn shell we go

ciscn 2023 初赛 pwn shell we go 这题go pwn&#xff0c;符号恢复就恢复很长时间了&#xff0c;网上的插件好多都没用 根着流程&#xff0c;可以看到这里有一个验证&#xff0c;以空格来分割&#xff0c;第一个参数会验证是否为nAcDsMicN 如果第一个参数验证通过&#xff0c…

Centos7 开启图形化界面 Linux安装VNC

环境: Centos7 windows连接&#xff1a;下载VNC Viewer 目录 VNC概述 VNC原理 一、检查是否安装过VNC 二、安装图形化界面 三、安装和配置VNC服务 四: 启动VNC及常用命令 五: VNC windos连接工具连接 VNC概述 VNC (Virtual Network Computing)是虚拟网络计算机的缩写…

如何零基础自学网络安全?

学前感言: 1.这是一条坚持的道路,三分钟的热情可以放弃往下看了. 2.多练多想,不要离开了教程什么都不会了.最好看完教程自己独立完成技术方面的开发. 3.有时多 google,baidu,我们往往都遇不到好心的大神,谁会无聊天天给你做解答. 4.遇到实在搞不懂的,可以先放放,以后再来解…

性能优化记录

您好&#xff0c;如果喜欢我的文章&#xff0c;可以关注我的公众号「量子前端」&#xff0c;将不定期关注推送前端好文~ 前言 最近零零散散的对刚接手的一个新项目做了一些优化&#xff0c;白屏、打包相关的内容都涉及到了&#xff0c;写一篇文章来记录一下。 白屏相关 DNS…

chatgpt赋能python:Python同一行语句之间的分隔

Python同一行语句之间的分隔 在Python中&#xff0c;同一行内的语句通常使用分号 ‘;’ 分隔开来。分号作为语句之间的分隔符&#xff0c;可以使我们在同一行内写多条语句&#xff0c;从而减少代码行数&#xff0c;提高代码可读性和可维护性。 分隔符和代码风格 在使用分号进…

[C++]基本知识与概念

C基础知识与概念 C与C基础C面向对象C STLC 内存管理C11新特性C linux编程I/O多路复用 前言 本文章适用于有一定C基础的新手同学快速掌握一些C的基本知识概念以及面试中可能会问的内容&#xff0c;如果你没有相应的基础学习又因为这篇文章缺少代码讲解所以可能会影响你的学习效率…