深度解密 __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 分钟搞懂 ECN
5 分钟搞懂 ECN
3203 0
|
机器学习/深度学习 算法 数据挖掘
【MATLAB】数据拟合第10期-二阶多项式的局部加权回归拟合算法
【MATLAB】数据拟合第10期-二阶多项式的局部加权回归拟合算法
484 0
|
Linux 异构计算 Python
【linux】nvidia-smi 查看GPU使用率100%
nvidia-smi 查看GPU使用率一直是100%解决办法
【linux】nvidia-smi 查看GPU使用率100%
|
传感器 大数据 数据处理
深入理解Python中的生成器:用法及应用场景
【10月更文挑战第7天】深入理解Python中的生成器:用法及应用场景
514 1
|
定位技术 API C++
Python GIS神器shapely 2.0新版本来了
Python GIS神器shapely 2.0新版本来了
336 9
|
前端开发 API UED
React组件生命周期详解
【9月更文挑战第4天】在React应用开发中,掌握组件生命周期对于管理状态和属性至关重要,并能有效提升应用性能。本文详细介绍了React组件生命周期的三个阶段:挂载、更新和卸载,并通过代码示例展示了如何避免状态更新导致的死循环及优化网络请求等问题,帮助开发者构建更高效、可维护的应用。
302 3
|
机器学习/深度学习 算法 数据可视化
如何在机器学习中检测异常值
如何在机器学习中检测异常值
385 2
|
存储 关系型数据库 MySQL
MySQL 多表查询详解
MySQL 是一个强大的关系型数据库管理系统,多表查询是数据库操作中的重要部分之一。多表查询允许您从多个表中检索和操作数据,以满足复杂的数据需求。本文将介绍 MySQL 多表查询的基本概念、语法和示例,以及一些常见的多表查询场景。
1126 0
|
SQL 缓存 关系型数据库
SqlAlchemy 2.0 中文文档(四十四)(2)
SqlAlchemy 2.0 中文文档(四十四)
219 4
|
JSON 数据库 数据格式
使用 Django Q 对象构建复杂查询条件
通过本文示例,我们展示了如何使用Django的Q对象来构建复杂的查询条件,以及如何实现分页功能。Q对象的强大之处在于它能够轻松地组合多个查询条件,支持“与”、“或”关系,极大地提高了查询的灵活性和可读性。希望本文对你在实际项目中使用Django ORM构建复杂查询有所帮助。