目录
  • python super()使用注意事项

    前言

    python是一门面向对象的语言,定义类时经常要用到继承,在类的继承中,子类继承父类中已经封装好的方法,不需要再次编写,如果子类如果重新定义了父类的某一方法,那么该方法就会覆盖父类的同名方法,但是有时我们希望子类保持父类方法的基础上进行扩展,而不是直接覆盖,就需要先调用父类的方法,然后再进行功能的扩展,这时就可以通过super来实现对父类方法的调用。

    super的用法

    看下面一个例子:

    class a:
        def func(self):
            print("a的func执行")
    
    
    class b(a):
    
        def func(self):
            super().func()
            print("b扩展的func执行")
    
    
    b = b()
    b.func()
    # 输出结果为:
    # a的func执行
    # b扩展的func执行
    

    上面程序中,a是父类,b是a的子类,我们在a类中重定义了func()方法,在b类中重新定义了func()方法,在方法中通过super().func()又调用了父类的方法,所以执行结果才会有a类func()方法输出。

    如果经常看python内置库及第三方库源码的话,你会发现,super用的非常多的地方是在子类中调用父类的初始化__init__()方法,这种用法非常常见。

    class a:
        def __init__(self, x):
            self.x = x
    
    class b(a):
    
        def __init__(self, x, y):
            super().__init__(x)
            self.y = y
        
    
    b = b(1, 2)
    print(b.x, b.y)
    

    看到这,你会想到super就是用来获取父类并用来调用父类方法的,这样说对不对呢,其实是不对的,使用supper获取的不是父类,而是mro列表中的下一个类,所谓mro列表即方法解析顺序(method resolution order)列表,它代表着类继承的顺序,我们可以使用以下几种获得某个类的mro列表:

    c.mro()
    c.__mro__
    c.__class__.__mro__
    

    mro列表的顺序确定经历了很多次的变迁,最新的是通过c3线性化算法来实现的,感兴趣的话可以自行了解一下,总的来说,一个类的mro列表就是合并所有父类的mro列表,并遵循以下三条原则:

    • 子类永远在父类前面
    • 如果有多个父类,会根据它们在列表中的顺序被检查
    • 如果对下一个类存在两个合法的选择,选择第一个父类

    下面来看一下下面这个例子:

    class a(base):
        def func(self):
            print("a的func执行")
            super().func()
            print("a的func执行完毕")
    
    
    class b(base):
        def func(self):
            print("b的func执行")
            super().func()
            print("b的func执行完毕")
    
    class c(a, b):
        def func(self):
            print("c的func执行")
            super().func()
            print("c的func执行完毕")
    
    
    c = c()
    c.func()
    # 获取mro列表
    print(c.__class__.__mro__)
    

    执行结果如下:

    上述程序中,base是父类,a、b都继承自base,c继承自 a、b,它们的继承关系就是一个典型的菱形继承,如下:

    通过结果我们可以看出,super并不是获取父类并用来调用父类的方法,而是根据mro列表一次调用下一个类,使用c.__class__.__mro__可以获取mro列表,mro列表的顺序是c、a、b、base、object。

    super的原理

    super计算方法解析顺序中的下一个类,可以接收两个参数:

    def super(cls, inst):
        mro = inst.__class__.mro()
        return mro[mro.index(cls) + 1]
    
    • 通过inst负责生成mro列表
    • 通过cls定位在mro列表中的index, 并返回mro[index + 1]

    python super()使用注意事项

    python 中,由于基类不会在 __init__() 中被隐式地调用,需要程序员显式调用它们。这种情况下,当程序中包含多重继承的类层次结构时,使用 super 是非常危险的,往往会在类的初始化过程中出现问题。

    混用super与显式类调用

    分析如下程序,c 类使用了 __init__() 方法调用它的基类,会造成 b 类被调用了 2 次:

    class a:
        def __init__(self):
            print("a",end=" ")
            super().__init__()
    class b:
        def __init__(self):
            print("b",end=" ")
            super().__init__()
    class c(a,b):
        def __init__(self):
            print("c",end=" ")
            a.__init__(self)
            b.__init__(self)
    print("mro:",[x.__name__ for x in c.__mro__])
    c()
    

    运行结果为:

    mro: [‘c’, ‘a’, ‘b’, ‘object’]
    c a b b

    出现以上这种情况的原因在于,c 的实例调用 a.__init__(self),使得 super(a,self).__init__() 调用了 b.__init__() 方法。换句话说,super 应该被用到整个类的层次结构中。

    但是,有时这种层次结构的一部分位于第三方代码中,我们无法确定外部包的这些代码中是否使用 super(),因此,当需要对某个第三方类进行子类化时,最好查看其内部代码以及 mro 中其他类的内部代码。

    不同种类的参数

    使用 super 的另一个问题是初始化过程中的参数传递。如果没有相同的签名,一个类怎么能调用其基类的 __init__() 代码呢?这会导致下列问题:

    class commonbase:
        def __init__(self):
            print("commonbase")
            super().__init__()
    class base1(commonbase):
        def __init__(self):
            print("base1")
            super().__init__()
    class base2(commonbase):
        def __init__(self):
            print("base2")
            super().__init__()
    class myclass(base1,base2):
        def __init__(self,arg):
            print("my base")
            super().__init__(arg)
    myclass(10)
    

    运行结果为:

    my base
    traceback (most recent call last):
      file “c:\users\mengma\desktop\demo.py”, line 20, in <module>
        myclass(10)
      file “c:\users\mengma\desktop\demo.py”, line 19, in __init__
        super().__init__(arg)
    typeerror: __init__() takes 1 positional argument but 2 were given

    一种解决方法是使用 *args 和 **kwargs 包装的参数和关键字参数,这样即使不使用它们,所有的构造函数也会传递所有参数,如下所示:

    class commonbase:
        def __init__(self,*args,**kwargs):
            print("commonbase")
            super().__init__()
    class base1(commonbase):
        def __init__(self,*args,**kwargs):
            print("base1")
            super().__init__(*args,**kwargs)
    class base2(commonbase):
        def __init__(self,*args,**kwargs):
            print("base2")
            super().__init__(*args,**kwargs)
    class myclass(base1,base2):
        def __init__(self,arg):
            print("my base")
            super().__init__(arg)
    myclass(10)
    

    运行结果为:

    my base
    base1
    base2
    commonbase

    不过,这是一种很糟糕的解决方法,由于任何参数都可以传入,所有构造函数都可以接受任何类型的参数,这会导致代码变得脆弱。另一种解决方法是在 myclass 中显式地使用特定类的 __init__() 调用,但这无疑会导致第一种错误。

    总结

    现在我们知道:supper获取的是mro列表中的下一个类,当前类的父类没有实质性的关系;还有如何查看mro列表。最后需要注意的是super以及mro列表,针对都是python新式类!

    英语好的话可以读一下这边文章python’s super() considered super

    到此这篇关于python中super()函数的理解与基本使用的文章就介绍到这了,更多相关python中super()函数内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!