Python中的super函数,你熟吗?

1. 概述

Python中的super()函数可以在子类中调用父类的方法,而不必显式地指定父类的名称,这可以大大提高代码的灵活性和可读性,特别是在多重继承的情况下。在本文中,我们将介绍super的基本用法,以及一些高级使用技巧。

2. 基本用法

2.1 在单继承中的使用

在单继承中,如果子类要调用父类的方法,一般使用父类的名称来显式地指定要调用的方法。例如:

class Animal:

def sound(self):

print("")

class Cat(Animal):

def sound(self):

print("Meow")

cat = Cat()

cat.sound() # 输出Meow

使用super()函数的优势在于可以允许实例化的子类共享同一个父类,并且保留对父类的方法调用。这在代码重构时非常有用,因为子类可以继承父类的基本行为,同时可以更方便地提供额外的行为或更改父类的行为。

使用super()函数的语法如下:

class Animal:

def sound(self):

print("")

class Cat(Animal):

def sound(self):

super().sound()

print("Meow")

cat = Cat()

cat.sound() # 输出空行和Meow

上面的代码中,我们使用了super().sound()调用了父类的sound()方法。这样做的好处是,无论Animal类的名称如何更改,Cat都可以通过super()函数访问到正确的父类。

2.2 在多继承中的使用

在多继承中,使用super()函数更加有用。假设我们有一个类Bird,它继承了Animal类,而Animal类又继承了LivingThing类:

class LivingThing:

def breathe(self):

print("")

class Animal(LivingThing):

def sound(self):

print("")

class Bird(Animal):

def fly(self):

print("")

bird = Bird()

bird.fly()

bird.sound()

bird.breathe()

在这个例子中,我们可以使用super()函数来调用父类的方法,而无需明确指定父类的名称:

class LivingThing:

def breathe(self):

print("")

class Animal(LivingThing):

def sound(self):

print("")

class Bird(Animal):

def sound(self):

super().sound()

print("Tweet")

def fly(self):

print("")

bird = Bird()

bird.fly()

bird.sound()

bird.breathe()

需要注意的是,在多继承的情况下,super()函数并不总是按照我们期望的方式工作。例如:

class A:

def method(self):

print("A")

class B(A):

def method(self):

print("B")

super().method()

class C(A):

def method(self):

print("C")

super().method()

class D(B, C):

def method(self):

print("D")

super().method()

d = D()

d.method() # 输出D、B、C、A

在这个例子中,我们定义了一个多继承关系,其中类B和类C继承自类A,并覆盖了它的方法。在类D中,我们再次覆盖了A的方法,并使用了super()函数来调用其他类中的方法。然而,输出的顺序并不是我们期望的B、C、A。相反,输出顺序是D、B、C、A。

这是因为Python中的super()函数是基于方法解析顺序(Method Resolution Order,简称MRO)的,MRO是一个确定多个继承关系中属性和方法调用顺序的算法。Python使用C3算法来计算MRO。要查看类的MRO,可以使用__mro__属性:

print(D.__mro__) # 输出(, , , , )

3. 高级用法

3.1 动态绑定方法

有时候我们会在运行时动态地绑定方法,这时候需要使用到types.MethodType()super()函数。

假如我们有一个类Human,它有一个方法walk:

class Human:

def __init__(self, name):

self.name = name

def walk(self):

print("{} is walking".format(self.name))

human = Human("John")

human.walk() # 输出John is walking

现在我们希望动态地把walk方法绑定到另一个实例的对象上,而不是在类定义中指定。这可以通过以下代码实现:

class MyClass:

def __init__(self, name):

self.name = name

def run(self):

print("{} is running".format(self.name))

object1 = MyClass("Anna")

object2 = MyClass("Ben")

method = human.walk

method = types.MethodType(method, object1)

method() # 输出John is walking

method = human.walk

method = types.MethodType(method, object2)

method() # 输出John is walking

上面的代码中,我们将一个方法对象human.walk绑定到不同的MyClass 实例上,使用types.MethodType()函数将方法绑定到实例。调用method()方法时,self.name将引用不同的对象,输出结果也会不同。

3.2 经典类和新式类的区别

Python中的类分为新式类和经典类。在早期版本的Python中,新式类需要显式地继承自object类,而经典类不需要这样做。因此,在不同的继承体系中使用super()函数会产生不同的结果。

新式类的super()函数根据MRO调用父类方法,而经典类的super()函数则根据深度遍历调用父类方法。

例如,假设我们有一个名为MyClass的经典类:

class MyClass:

def my_method(self):

print("MyClass.my_method()")

class AnotherClass(MyClass):

def my_method(self):

print("AnotherClass.my_method()")

MyClass.my_method(self)

在这个例子中,我们定义了类MyClass和AnotherClass,AnotherClass继承自MyClass并覆盖了其方法。在AnotherClass的my_method()方法中,我们使用MyClass.my_method(self)调用父类的方法。

现在我们来看看使用新式类的情况:

class MyNewClass(object):

def my_method(self):

print("MyNewClass.my_method()")

class AnotherNewClass(MyNewClass):

def my_method(self):

print("AnotherNewClass.my_method()")

super().my_method()

在这个例子中,我们定义了类MyNewClass和AnotherNewClass。AnotherNewClass继承自MyNewClass,并覆盖了其方法。在AnotherNewClass的my_method()方法中,我们使用super().my_method()调用父类的方法。

请注意,使用经典类时,我们需要显式地传递self参数,而使用新式类时,则不需要传递self参数。

3.3 super()和重载运算符

在面向对象编程中,我们经常会使用运算符重载来实现自定义操作。在Python中,运算符重载基于函数调用来实现,因此可以使用super()函数来调用父类的运算符方法。

例如,假设我们有一个类Vector,表示一个多维向量。我们希望使用运算符重载实现向量相加,代码如下:

class Vector:

def __init__(self, x, y):

self.x = x

self.y = y

def __add__(self, other):

return Vector(self.x + other.x, self.y + other.y)

v1 = Vector(1, 2)

v2 = Vector(3, 4)

v3 = v1 + v2

print(v3.x, v3.y) # 输出4 6

现在,我们希望创建一个名为LoggedVector的子类,它可以记录向量操作的次数。为此,我们需要覆盖__add__()方法,并在类中添加一个访问计数器的属性:

class LoggedVector(Vector):

cnt = 0

def __add__(self, other):

LoggedVector.cnt += 1

return super().__add__(other)

v1 = LoggedVector(1, 2)

v2 = LoggedVector(3, 4)

v3 = v1 + v2

v4 = v1 + v2

print(v3.x, v3.y) # 输出4 6

print(LoggedVector.cnt) # 输出2

在这个例子中,我们创建了LoggedVector类,它继承自Vector,并覆盖了__add__()方法。在__add__()方法中,我们调用了父类的方法,并在计数器上增加了1。通过使用super()函数,我们确保了正确的父类方法被调用。

4. 总结

在本文中,我们介绍了Python中的super()函数。通过使用super()函数,我们可以在子类中调用父类的方法,而无需明确指定父类的名称。这可以使代码更灵活,可读性更高,特别是在多重继承的情况下。我们还介绍了一些高级用法,例如动态绑定方法,经典类和新式类之间的区别,以及如何使用super()函数和运算符重载。掌握了这些知识,你可以更好地利用Python的继承特性,写出更优雅的代码。

后端开发标签