python魔术方法类构建篇

发布时间 2023-07-15 20:39:19作者: 天才九少

本篇章的很多魔术方法都是跟class的建立有关的

4,类构建篇

  • __init_subclass__
  • __set_name__
  • __class_getitem__和__mro_entries__
  • __prepare__
  • __instancecheck__和__subclasscheck__

 

__init_subclass__方法

__init_subclass__这个方法你要定义在基类里面,然后当你以这个类为基类,定义一个衍生类的时候,这个方法就会被调用,我们尝试一个简单的例子,就是打印这个class,然后这里我们以base类为基类建立衍生类A,那运行结果呢就打印出来了这个class A,这个传进去的参数cls,是你刚刚建立的衍生类

 

class Base:
    def __init_subclass__(cls):
        print(cls)


class A(Base):
    pass

 

<class '__main__.A'>

这里我们可以把这个例子稍微做复杂一点点,我们让这个衍生类里面增加一个attribute x,让它是一个空的Dictionary,我们看在我们定义了A之后,这个A.x就是一个空的Dictionary,

class Base:
    def __init_subclass__(cls):
        cls.x = {}


class A(Base):
    pass


print(A.x)
# {}

那这个方法呢还可以传参数,比如下面我们可以要求它传一个name,然后在定义衍生类的时候,我们把这个Jack作为name传进去,可以看到这个衍生类A的name attribute就被赋值了 

class Base:
    def __init_subclass__(cls, name):
        cls.x = {}
        cls.name = name


class A(Base, name="Jack"):
    pass


print(A.x)
print(A.name)
# {}
# Jack

 

__set_name__方法

__set_name__方法更多的是定义在这个descriptor class 里,即使这个class它不是一个descriptor class,它依然起作用,这个方法相当于一个hook,当你在类的定义里去构建一个这个class的instance的时候,这个方法就会被调用,执行一下下面的程序,我们看owner 是A,也就是在哪个class定义里面,去构建的这个instance,而name是它赋值的这个变量的名字。那这个方法呢,有的时候我们在做log的时候可以使用

class D:
    def __set_name__(self, owner, name):
        print(owner, name)


class A:
    x = D()
<class '__main__.A'> x

 

__class_getitem__方法

接下来的两个方法都是在强化typing的时候被引入的,第一个叫做__class_getitem__,__getitem__是这个class的object用方括号尝试access value的时候使用的魔术方法,那__class_getitem__就是这个类用方括号做 subscribe 的时候会调用的方法,下面我们写了一个简单的__class_getitem__函数,把这个item打印出来,然后返回abc,在下面我们打印一个A[0],注意这个A是class而不是class A的object,我们会发现item是0被打印出来了,然后A[0]是abc也被打印出来了

class A:
    def __class_getitem__(cls, item):
        print(item)
        return "abc"


print(A[0])
# 0
# abc

这个东西跟typing的关系,这个list[int]表示我这个type是一个由integer组成的list,我们可以把它赋值到一个变量上,然后用这个变量再给其他的list做type hint,而type hint本身就是python代码,所以这个list[int]就必然有其实现的方式,我们观察一下它就是一个class然后方括号里面方东西,那它就是用__class_getitem__实现的。

class A:
    def __class_getitem__(cls, item):
        print(item)
        return "abc"


print(A[0])
int_arr_type = list[int]
lst1: int_arr_type = []
lst2: int_arr_type = []

 

__mro_entries__方法

另外一个更晦涩一点的函数叫做__mro_entries__,我们在B做继承A的时候,更多的时候括号里面是A是一个class object,而现在我们使用的是A(),也就是class A的一个object,如果我们直接这么写的话会报错

class A:
    pass
    # def __mro_entries__(self, bases):
    #     print(bases)
    #     return ()


class B(A()):
    pass
TypeError: A() takes no arguments

这里为什么会报错呢,简单来说就是建立B的时候它寻找自己的基类出现了问题,那这个__mro_entries__呢就是帮他去找基类的,它需要返回一个tuple,就是你去哪找你的基类去,那我们加上这个方法之后,它就能运行了

class A:

    def __mro_entries__(self, bases):
        print(bases)
        return ()


class B(A()):
    pass
(<__main__.A object at 0x000001A3D34DFCD0>,)

我们在下面这种情况下这个__mro_entries__返回的是一个空的tuple,这个的意思就是说你从我这找不到任何基类,别地儿找去,正因如此,这个时候如果你去打印issubclass(B,A),你就会发现A它并不是B的基类,因为B试图官这个A的object去问 你这个object 我怎么拿到你的基类啊 它说这里没有你的基类啊 一边呆着去

class A:

    def __mro_entries__(self, bases):
        print(bases)
        return ()


class B(A()):
    pass


print(issubclass(B, A))
(<__main__.A object at 0x000002847D3CFCD0>,)
False

我们现在把这个代码稍微作点改变,现在这个__mro_entries__返回一个带A的tuple,这就相当于B问这个A的object说 你能给我什么基类啊 

然后它说就把A当成你的基类吧,这种情况下 issubclass(B,A)就是True了,因为B就会认为A是自己的基类,

class A:

    def __mro_entries__(self, bases):
        print(bases)
        return (A, )


class B(A()):
    pass


print(issubclass(B, A))
(<__main__.A object at 0x000001298760FCD0>,)
True

 

__prepare__方法

接下来看几个与metaclass相关的魔术方法,首先是这个__prepare__方法,它是用来准备你要构建的这个class的命令空间的,这里注意一下你需要手动的给这个__prepare__方法加上classmethod这个decorator,我们简单运行一下,可以看到当我们用这个metaclass Meta来构建A的时候,__prepare__方法被运行了,然后打印了A bases没有 keywords也没有

class Meta(type):
    @classmethod
    def __prepare__(cls, name, bases, **kwds):
        print(name, bases, kwds)
        return {}


class A(metaclass=Meta):
    pass
A () {}

这里我们可以在这个__prepare__函数里面,返回一个Dictionary,这个Dictionary里面是x:10,那在构建这个class A之后,A就会有x这个attribute,然后值是10

class Meta(type):
    @classmethod
    def __prepare__(cls, name, bases, **kwds):
        return {"x": 10}


class A(metaclass=Meta):
    pass


print(A.x)
# 10

 

__instancecheck__方法和__subclasscheck__方法

__instancecheck__和__subclasscheck__它们对应的函数就是isinstance跟issubclass这个两个函数,我们在做isinstance(123, A)的时候,就调用了A的元类的__instancecheck__这个函数,同样的当我们做issubclass(int, A)的时候,就调用了A的元类的__subclasscheck__函数,这个函数我们稍微写得复杂了一点点,说如果传进来的class是int的话,返回True否则返回False,可以看到由于传进来的这个type是int,所以返回了True,那这两个方法一般也是在你做metaclass的时候才需要用到的

class Meta(type):
    def __instancecheck__(self, instance):
        print("Instance Check")
        return True

    def __subclasscheck__(self, subclass):
        print("Subclass Check")
        if subclass is int:
            return True
        return False


class A(metaclass=Meta):
    pass


o = A()
print(isinstance(123, A))
print(issubclass(int, A))
Instance Check
True
Subclass Check
True