深入理解Python虚拟机:super超级魔法的背后原理

简介: 深入理解Python虚拟机:super超级魔法的背后原理

super 类作为Python虚拟机中强大的功能之一,super 可以说是 Python 对象系统基石,他可以帮助我们更灵活地使用继承和方法调用。


super类的使用


在 Python 中,我们经常使用继承来构建类的层次结构。当子类继承了父类的属性和方法时,有时我们需要在子类中调用父类的方法或属性。这就是 super 类的用武之地。


super 函数的一般用法是在子类中调用父类的方法,格式为 super().method() 。这样可以方便地使用父类的实现,并在子类中添加自己的特定行为。


下面是一个示例代码,演示了 super 函数的使用:

class Parent:
    def __init__(self, name):
        self.name = name
    
    def say_hello(self):
        print(f"Hello, I'm {self.name}")
 
class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age
    
    def say_hello(self):
        super().say_hello()
        print(f"I'm {self.name} and I'm {self.age} years old")
 
child = Child("Alice", 10)
child.say_hello()

输出结果为:

Hello, I'm Alice
I'm Alice and I'm 10 years old

在上述示例中,Child类继承自Parent类。在Child类的构造函数中,我们使用super().init(name)来调用父类Parent的构造函数,以便在子类中初始化父类的属性。


在say_hello方法中,我们使用super().say_hello()调用父类Parent的say_hello方法,并在子类中添加了额外的输出。


除了调用父类的方法,super函数还可以用于访问父类的属性。例如,super().attribute可以用来获取父类的属性值。


super类的工作原理-Super 设计的目的


要理解super类的工作原理,我们需要了解Python中的多重继承和方法解析顺序(Method Resolution Order,MRO)。多继承是指一个类可以同时继承多个父类。在Python中,每个类都有一个内置属性__mro__,它记录了方法解析顺序。MRO是根据C3线性化算法生成的,它决定了在多重继承中调用方法的顺序。当对象进行方法调用的时候,就会从类的 mro 当中的第一个类开始寻找,直到最后一个类为止,当第一次发现对应的类有相应的方法时就进行返回就调用这个类的这个方法。


Super 类的的签名为 class super(type, object_or_type=None),这个类返回的是一个 super 对象,也是一个代理对象,当使用这个对象进行方法调用的时候,这个调用会转发给 type 父类或同级类。object_or_type 参数的作用是用于确定要搜索的方法解析顺序(也就是通过object_or_type得到具体的 mro),对于方法的搜索从 type 后面的类开始。


例如,如果 的 object_or_type 的 mro 是 D -> B -> C -> A -> object 并且type的值是 B ,则进行方法搜索的顺序为C -> A -> object ,因为搜索是从 type 的下一个类开始的。 下面我们使用一个例子来实际体验一下:

class A:
    def __init__(self):
        super().__init__()
 
    def method(self):
        print("In method of A")
 
 
class B(A):
    def __init__(self):
        super().__init__()
 
    def method(self):
    print("In method of B")
 
 
class C(B):
    def __init__(self):
    super().__init__()
 
    def method(self):
        print("In method of C")
 
 
if __name__ == '__main__':
    print(C.__mro__)
    obj = C()
    s = super(C, obj)
    s.method()
    s = super(B, obj)
    s.method()

上面的程序输出结果为:

(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
In method of B
In method of A

在上面的代码当中继承顺序为,C 继承 B,B 继承 A,C 的 mro 为,(C, B, A, object),super(C, obj)表示从 C 的下一个类开始搜索,因此具体的搜索顺序为 ( B, A, object),因此此时调用 method 方法的时候,会调用 B 的 method 方法,super(B, obj)表示从 B 的下一个类开始搜索,因此搜索顺序为 (A, object),因此此时调用的是 A 的 method 方法。


Super 和栈帧的关系


需要注意的是我们在一个类的__init__方法当中并没有给 super 任何参数,那么他是如何找到 super 需要的两个参数呢?


这其中的魔法就是在 Super 类对象的初始化会获取当前栈帧的第一个参数对象,这个就是对应上面的object_or_type参数,type就是局部变量表当中的一个参数__class__,我们可以通过查看类方法的局部变量去验证这一点:

import inspect
 
 
class A(object):
    def __init__(self):
    super().__init__()
    print(inspect.currentframe().f_locals)
    def bar(self):
    pass
 
    def foo(self):
    pass
 
 
class Demo(A):
 
    def __init__(self):
    super().__init__()
    print(inspect.currentframe().f_locals)
 
    def bar(self):
    super().bar()
    print(inspect.currentframe().f_locals)
 
    def foo(self):
    print(inspect.currentframe().f_locals)
 
 
if __name__ == '__main__':
    demo = Demo()
    demo.bar()
    demo.foo()

上面的代码输出结果为:

{'self': <__main__.Demo object at 0x103059040>, '__class__': <class '__main__.A'>}
{'self': <__main__.Demo object at 0x103059040>, '__class__': <class '__main__.Demo'>}
{'self': <__main__.Demo object at 0x103059040>, '__class__': <class '__main__.Demo'>}
{'self': <__main__.Demo object at 0x103059040>}

从上面的例子我们可以看到当我们进行方法调用且方法当中有 super 的使用时,栈帧的局部变量表当中会多一个字段__class__,这个字段表示对应的类,比如在 Demo 类当中,这个字段就是 Demo,在类 A 当中这个字段就是 A 。为什么要进行这样的处理呢,这是因为需要调用相应位置类的父类方法,因此所有的使用 super 的位置的type都必须是所在类。而在前面我们已经说明了object_or_type表示的是栈帧当中的第一个参数,也就是对象 self,这一点从上面的局部变量表也可以看出来,通过这个对象我们可以知道对象本身的 mro 序列了。在 super 得到两个参数之后,也就能够实现对应的功能了。


CPython的实现


CPython 内部是如何实现 super 类的,首先来看一下他的__init__方法(删除了error checking 代码):

static int
super_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    superobject *su = (superobject *)self;
    PyTypeObject *type = NULL; // 表示从哪个类的后面开始查询,含义和 上文当中的 type 一样
    PyObject *obj = NULL; // 表示传递过来的对象
    PyTypeObject *obj_type = NULL; // 表示对象 obj 的类型
    // 获取 super 的两个参数 type 和 object_or_type
    if (!PyArg_ParseTuple(args, "|O!O:super", &PyType_Type, &type, &obj))
        return -1;
 
    if (type == NULL) {
        /* Call super(), without args -- fill in from __class__
           and first local variable on the stack. */
        PyFrameObject *f;
        PyCodeObject *co;
        Py_ssize_t i, n;
        f = _PyThreadState_GET()->frame; // 得到当前栈帧
        // 栈帧的第一个参数表示对象
        obj = f->f_localsplus[0];
        if (obj == NULL && co->co_cell2arg) {
            /* The first argument might be a cell. */
            n = PyTuple_GET_SIZE(co->co_cellvars);
            for (i = 0; i < n; i++) {
                if (co->co_cell2arg[i] == 0) {
                    PyObject *cell = f->f_localsplus[co->co_nlocals + i];
                    assert(PyCell_Check(cell));
                    obj = PyCell_GET(cell);
                    break;
                }
            }
        }
        if (co->co_freevars == NULL)
            n = 0;
        else {
            assert(PyTuple_Check(co->co_freevars));
            n = PyTuple_GET_SIZE(co->co_freevars);
        }
        // 下面的代码表示获取 type 对象,也就是从局部变量表当中获取到 __class__ 
        for (i = 0; i < n; i++) {
            PyObject *name = PyTuple_GET_ITEM(co->co_freevars, i);
            assert(PyUnicode_Check(name));
            if (_PyUnicode_EqualToASCIIId(name, &PyId___class__)) {
                Py_ssize_t index = co->co_nlocals +
                    PyTuple_GET_SIZE(co->co_cellvars) + i;
                PyObject *cell = f->f_localsplus[index];
                type = (PyTypeObject *) PyCell_GET(cell);
                break;
            }
        }
    }
 
    if (obj == Py_None)
        obj = NULL;
    if (obj != NULL) {
        // 这个函数是用于获取 obj 的 type
        obj_type = supercheck(type, obj);
        if (obj_type == NULL)
            return -1;
        Py_INCREF(obj);
    }
    return 0;
}

在上面的代码执行完成之后就得到了一个 super 对象,之后在进行函数调用的时候就会将对应类的方法和对象 obj 绑定成一个方法对象返回,然后在进行方法调用的时候就能够成功调用了。

class Demo:
 
    def __init__(self):
    print(super().__init__)
 
 
if __name__ == '__main__':
    Demo()

输出结果:

<method-wrapper '__init__' of Demo object at 0x100584070>

总结 super 是 Python 面向对象编程当中非常重要的一部分内容,在本篇文章当中详细介绍了 super 内部的工作原理和 CPython 内部部分源代码分析了 super 的具体实现。在 Python 当中 super 的使用方式分为两种一种是可以直接使用参数,另外一种是在类的方法当中不使用参数,后者的实现稍微复杂一点,他会从当前栈帧和局部变量表当中分别取出类对象和类,作为 super 的参数,从而实现 super 的功能。

相关文章
|
3月前
|
运维 监控 算法
时间序列异常检测:MSET-SPRT组合方法的原理和Python代码实现
MSET-SPRT是一种结合多元状态估计技术(MSET)与序贯概率比检验(SPRT)的混合框架,专为高维度、强关联数据流的异常检测设计。MSET通过历史数据建模估计系统预期状态,SPRT基于统计推断判定偏差显著性,二者协同实现精准高效的异常识别。本文以Python为例,展示其在模拟数据中的应用,证明其在工业监控、设备健康管理及网络安全等领域的可靠性与有效性。
661 13
时间序列异常检测:MSET-SPRT组合方法的原理和Python代码实现
|
3月前
|
机器学习/深度学习 算法 Python
机器学习特征筛选:向后淘汰法原理与Python实现
向后淘汰法(Backward Elimination)是机器学习中一种重要的特征选择技术,通过系统性地移除对模型贡献较小的特征,以提高模型性能和可解释性。该方法从完整特征集出发,逐步剔除不重要的特征,最终保留最具影响力的变量子集。其优势包括提升模型简洁性和性能,减少过拟合,降低计算复杂度。然而,该方法在高维特征空间中计算成本较高,且可能陷入局部最优解。适用于线性回归、逻辑回归等统计学习模型。
162 7
|
25天前
|
机器学习/深度学习 算法 测试技术
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
本文探讨了基于图的重排序方法在信息检索领域的应用与前景。传统两阶段检索架构中,初始检索速度快但结果可能含噪声,重排序阶段通过强大语言模型提升精度,但仍面临复杂需求挑战
64 0
图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
|
5月前
|
存储 缓存 Java
Python高性能编程:五种核心优化技术的原理与Python代码
Python在高性能应用场景中常因执行速度不及C、C++等编译型语言而受质疑,但通过合理利用标准库的优化特性,如`__slots__`机制、列表推导式、`@lru_cache`装饰器和生成器等,可以显著提升代码效率。本文详细介绍了这些实用的性能优化技术,帮助开发者在不牺牲代码质量的前提下提高程序性能。实验数据表明,这些优化方法能在内存使用和计算效率方面带来显著改进,适用于大规模数据处理、递归计算等场景。
155 5
Python高性能编程:五种核心优化技术的原理与Python代码
|
6月前
|
算法 数据处理 Python
高精度保形滤波器Savitzky-Golay的数学原理、Python实现与工程应用
Savitzky-Golay滤波器是一种基于局部多项式回归的数字滤波器,广泛应用于信号处理领域。它通过线性最小二乘法拟合低阶多项式到滑动窗口中的数据点,在降噪的同时保持信号的关键特征,如峰值和谷值。本文介绍了该滤波器的原理、实现及应用,展示了其在Python中的具体实现,并分析了不同参数对滤波效果的影响。适合需要保持信号特征的应用场景。
548 11
高精度保形滤波器Savitzky-Golay的数学原理、Python实现与工程应用
|
5月前
|
安全 数据挖掘 编译器
【01】优雅草央央逆向技术篇之逆向接口协议篇-如何用python逆向接口协议?python逆向接口协议的原理和步骤-优雅草央千澈
【01】优雅草央央逆向技术篇之逆向接口协议篇-如何用python逆向接口协议?python逆向接口协议的原理和步骤-优雅草央千澈
119 6
|
6月前
|
缓存 数据安全/隐私保护 Python
python装饰器底层原理
Python装饰器是一个强大的工具,可以在不修改原始函数代码的情况下,动态地增加功能。理解装饰器的底层原理,包括函数是对象、闭包和高阶函数,可以帮助我们更好地使用和编写装饰器。无论是用于日志记录、权限验证还是缓存,装饰器都可以显著提高代码的可维护性和复用性。
83 5
|
编译器 C++ Python
3分钟,让虚拟机跑起Python
Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。
574 0
3分钟,让虚拟机跑起Python
|
3月前
|
机器学习/深度学习 存储 设计模式
Python 高级编程与实战:深入理解性能优化与调试技巧
本文深入探讨了Python的性能优化与调试技巧,涵盖profiling、caching、Cython等优化工具,以及pdb、logging、assert等调试方法。通过实战项目,如优化斐波那契数列计算和调试Web应用,帮助读者掌握这些技术,提升编程效率。附有进一步学习资源,助力读者深入学习。
|
1月前
|
数据采集 安全 BI
用Python编程基础提升工作效率
一、文件处理整明白了,少加两小时班 (敲暖气管子)领导让整理100个Excel表?手都干抽筋儿了?Python就跟铲雪车似的,哗哗给你整利索!
75 11

热门文章

最新文章

推荐镜像

更多