深度解密 __init_subclass__ 到底是个啥?

简介: 深度解密 __init_subclass__ 到底是个啥?

假设我们定义了一个类,但不希望这个类被继承,该怎么做呢?

class NotInheritable:
    """
    不可被继承的类
    """
    pass
# 希望继承 NotInheritable 的时候报错
# 那么要怎么修改这段逻辑呢?
class A(NotInheritable):
    pass

所以 Python 提供了一个名为 __init_subclass__ 的特殊成员函数,当某个类被继承时,会自动触发。

class NotInheritable:
    """
    不可被继承的类
    """
    def __init_subclass__(cls, **kwargs):
        print(f"被 <class '{cls.__name__}'> 继承了")
class A(NotInheritable):
    pass
"""
被 <class 'A'> 继承了
"""

在介绍 Python 虚拟机的时候我们说过,如果想创建一个实例对象,那么需要调用类对象;如果想创建一个类对象,那么需要调用 type。

class NotInheritable:
    """
    不可被继承的类
    """
    def __init_subclass__(cls, **kwargs):
        print(f"被 <class '{cls.__name__}'> 继承了")
class A(NotInheritable):
    pass
"""
被 <class 'A'> 继承了
"""
A = type("A", (NotInheritable,), {})
"""
被 <class 'A'> 继承了
"""

代码中的两种创建 class A 的方式是等价的,本质上都是调用 type。而 type 虽然是元类,但它本质上也是个类,调用的时候会执行内部的 __new__ 函数。

而在 __new__ 函数的内部,会检测要创建的类所继承的父类,有没有定义 __init_subclass__。如果有,则执行。

所以我们上面的问题,就可以这么解决。

class NotInheritable:
    """
    不可被继承的类
    """
    def __init_subclass__(cls, **kwargs):
        raise RuntimeError("NotInheritable 不可被继承")
class A(NotInheritable):
    pass
# 此时程序会直接报错
"""
RuntimeError: NotInheritable 不可被继承
"""

此时我们的目的就达到了,让 NotInheritable 无法被继承。但其实 __init_subclass__ 最大的用处是它在一些简单的场景中可以代替元类,来看看它的两个参数。

class Base:
    def __init_subclass__(cls, **kwargs):
        """
        注意参数 cls,它表示继承 Base 的类
        kwargs 则表示类的一些额外参数
        """
        print(cls)
        print(kwargs)
class A(Base, name="古明地觉", age=16):
    pass
"""
<class '__main__.A'>
{'name': '古明地觉', 'age': 16}
"""

代码打印的结果已经很清晰了,但可能有人会有一个疑问,当第一个参数表示 cls、也就是类的时候,__init_subclass__ 应该被 @classmethod 装饰才对啊。

理论上确实是这样,但实际不需要,而原因我们看一下源码就知道了。我们说创建类的时候,会调用 type.__new__,在源码中对应 type_new 函数,它是类对象创建时的第一案发现场。

源码位于 Objects/typeobject.c 中

所以 __init_subclass__ 其实是被隐式地装饰了,当然我们在代码中也可以手动装饰。如果我们不装饰,那么解释器底层会自动装饰。

其实源码中最上面的注释也写的很清晰了,如果 __init_subclass__ 和 __class_getitem__ 是普通的成员函数,就将它们变成类方法。

class Base:
    @classmethod
    def __init_subclass__(cls, **kwargs):
        print(cls)
        print(kwargs)
class A(Base, say="曾经我也想过一了百了"):
    @classmethod
    def __class_getitem__(cls, item):
        return f"item = {item}"
"""
<class '__main__.A'>
{'say': '曾经我也想过一了百了'}
"""
# 而 __class_getitem__ 则是让类能够支持索引和切片
print(A["古明地觉"])
print(A[123])
print(A[1: 10: 2])
print(A["x": "y": "z"])
"""
item = 古明地觉
item = 123
item = slice(1, 10, 2)
item = slice('x', 'y', 'z')
"""

这两个成员函数可以手动装饰,如果不装饰,那么解释器会自动隐式装饰、或者说帮我们装饰。因此我们在使用的时候,就不需要手动装饰了。

相信 __init_subclass__ 的原理已经很清晰了,然后是它的应用场景,我们说它可以用于元编程。

class Base:
    def __init_subclass__(cls, **kwargs):
        # 遍历 kwargs,将 key 作为属性、value 作为值
        # 绑定到 cls 里面
        for k, v in kwargs.items():
            type.__setattr__(cls, k, v)
        # 小知识:在给对象动态绑定属性时
        # 如果是类对象,应该使用 type.__setattr__(类, k, v)
        # 如果是实例对象,应该使用 object.__setattr__(实例, k, v)
        # 当然我们也可以绑定一些预定义的属性
        # 也就是所有继承该基类的类,都会有的属性
        type.__setattr__(cls, "ping", "pong")
        # 也可以绑定一个成员函数
        # 注意函数里面要有一个 self 参数
        type.__setattr__(cls, "say_hi",
                         lambda self: "hello world")
class A(Base, name="魔理沙", address="魔法森林"):
    pass
print(A.name)  # 魔理沙
print(A.address)  # 魔法森林
print(A.ping)  # pong
print(A().say_hi())  # hello world
class B(Base):
    pass
print(B.ping)  # pong
print(B().say_hi())  # hello world

可以看到,我们在不使用元类的情况下,通过 __init_subclass__ 实现了类的自定义过程。当然示例比较简单,你也可以实现更复杂的逻辑,总之在某些场景下我们能用 __init_subclass__ 替代元类。

相关文章
|
5月前
|
机器学习/深度学习 计算机视觉 存储
fast.ai 深度学习笔记(七)(4)
fast.ai 深度学习笔记(七)
119 8
fast.ai 深度学习笔记(七)(4)
|
5月前
|
机器学习/深度学习 API 调度
fast.ai 深度学习笔记(六)(3)
fast.ai 深度学习笔记(六)
109 6
fast.ai 深度学习笔记(六)(3)
|
5月前
|
机器学习/深度学习 自然语言处理 计算机视觉
fast.ai 深度学习笔记(五)(1)
fast.ai 深度学习笔记(五)
132 5
fast.ai 深度学习笔记(五)(1)
|
5月前
|
机器学习/深度学习 算法 算法框架/工具
fast.ai 深度学习笔记(六)(4)
fast.ai 深度学习笔记(六)
125 4
fast.ai 深度学习笔记(六)(4)
|
5月前
|
存储 机器学习/深度学习 计算机视觉
fast.ai 深度学习笔记(七)(3)
fast.ai 深度学习笔记(七)
114 4
fast.ai 深度学习笔记(七)(3)
|
5月前
|
API 机器学习/深度学习 Python
fast.ai 深度学习笔记(四)(1)
fast.ai 深度学习笔记(四)
112 3
fast.ai 深度学习笔记(四)(1)
|
5月前
|
机器学习/深度学习 PyTorch 算法框架/工具
fast.ai 深度学习笔记(六)(2)
fast.ai 深度学习笔记(六)
141 3
fast.ai 深度学习笔记(六)(2)
|
5月前
|
存储 机器学习/深度学习 编解码
fast.ai 深度学习笔记(七)(2)
fast.ai 深度学习笔记(七)
79 3
fast.ai 深度学习笔记(七)(2)
|
5月前
|
机器学习/深度学习 自然语言处理 搜索推荐
fast.ai 深度学习笔记(二)(3)
fast.ai 深度学习笔记(二)
118 2
fast.ai 深度学习笔记(二)(3)
|
5月前
|
索引 机器学习/深度学习 自然语言处理
fast.ai 深度学习笔记(二)(2)
fast.ai 深度学习笔记(二)
152 2
fast.ai 深度学习笔记(二)(2)