MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python继承与多态

2022-01-036.5k 阅读

Python继承基础

在Python中,继承是一种重要的面向对象编程(OOP)特性,它允许我们基于现有的类创建新的类。新创建的类称为子类(或派生类),而现有的类称为父类(或基类)。子类可以继承父类的属性和方法,同时还可以添加自己独特的属性和方法。

简单继承示例

我们来看一个简单的例子,假设我们有一个Animal类,它具有一些基本的属性和方法:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound.")

现在,我们可以创建一个继承自Animal类的Dog类:

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

    def bark(self):
        print(f"{self.name} barks.")

在上述代码中,Dog类继承自Animal类。通过super().__init__(name),我们调用了父类Animal__init__方法,这样Dog类的实例就具有了name属性。同时,Dog类还添加了自己的breed属性和bark方法。

我们可以这样使用这些类:

my_dog = Dog("Buddy", "Golden Retriever")
my_dog.speak()
my_dog.bark()
print(f"Name: {my_dog.name}, Breed: {my_dog.breed}")

上述代码输出如下:

Buddy makes a sound.
Buddy barks.
Name: Buddy, Breed: Golden Retriever

继承与属性查找

当我们在子类的实例上访问一个属性或方法时,Python会按照特定的顺序进行查找。首先,它会在子类中查找,如果找到了对应的属性或方法,就直接使用。如果在子类中没有找到,它会继续在父类中查找。如果在父类中也没有找到,它会沿着继承链继续向上查找,直到找到或者到达继承链的顶端(object类)。如果最终都没有找到,就会引发AttributeError

例如,假设我们在Dog类中没有定义speak方法,但是由于它继承自Animal类,所以当我们调用my_dog.speak()时,Python会在Animal类中找到speak方法并执行。

多重继承

Python支持多重继承,即一个子类可以从多个父类继承属性和方法。这在某些情况下非常有用,例如当一个类需要融合多个不同类的功能时。

多重继承示例

假设我们有两个类FlyerSwimmer,分别表示会飞和会游泳的能力:

class Flyer:
    def fly(self):
        print("I can fly.")


class Swimmer:
    def swim(self):
        print("I can swim.")

现在,我们创建一个Duck类,它同时继承自FlyerSwimmer类:

class Duck(Flyer, Swimmer):
    def quack(self):
        print("Quack!")

Duck类的实例就可以调用flyswimquack方法:

donald = Duck()
donald.fly()
donald.swim()
donald.quack()

上述代码输出如下:

I can fly.
I can swim.
Quack!

多重继承的问题与解决

多重继承虽然强大,但也可能带来一些问题,其中最著名的就是“菱形继承问题”。假设我们有以下类继承结构:

class A:
    def method(self):
        print("Method from A")


class B(A):
    pass


class C(A):
    def method(self):
        print("Method from C")


class D(B, C):
    pass

在这个例子中,D类通过BC间接继承自A。如果我们创建一个D类的实例并调用method方法,Python会按照特定的顺序查找方法。在Python中,这种查找顺序遵循C3线性化算法。按照这个算法,D类的方法解析顺序(MRO)为[D, B, C, A, object]。所以当我们调用d.method()时,会调用C类中的method方法,因为在MRO中C类在A类之前。

为了避免菱形继承问题带来的混淆,在使用多重继承时,应该谨慎设计类的继承结构,确保继承关系清晰明了。同时,也可以通过显式调用父类方法的方式来明确调用哪个父类的方法。

方法重写

在继承中,子类可以重写父类的方法。这意味着子类可以提供与父类中同名方法不同的实现。

方法重写示例

回到之前的AnimalDog类的例子。假设我们希望Dog类的speak方法有不同的实现:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound.")


class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

    def speak(self):
        print(f"{self.name} barks.")

在这个例子中,Dog类重写了Animal类的speak方法。当我们创建一个Dog类的实例并调用speak方法时,会执行Dog类中重写后的speak方法:

my_dog = Dog("Max", "German Shepherd")
my_dog.speak()

输出为:

Max barks.

调用父类方法

在子类重写的方法中,有时我们可能还需要调用父类的方法来执行一些通用的操作,然后再执行子类特有的操作。我们可以使用super()函数来调用父类的方法。

例如,假设我们希望在Dog类的speak方法中,先调用父类的speak方法,然后再输出一些额外的信息:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name} makes a sound.")


class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

    def speak(self):
        super().speak()
        print(f"{self.name} is a {self.breed} and it barks.")

现在,当我们调用my_dog.speak()时:

my_dog = Dog("Buddy", "Golden Retriever")
my_dog.speak()

输出为:

Buddy makes a sound.
Buddy is a Golden Retriever and it barks.

Python多态

多态是面向对象编程的另一个重要特性,它允许我们使用相同的操作对不同类型的对象进行处理,而这些对象可以属于不同的类,只要它们具有兼容的接口。在Python中,多态主要通过继承和方法重写来实现。

多态示例

我们假设有一个Shape类,以及它的两个子类CircleRectangle

class Shape:
    def area(self):
        pass


class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        import math
        return math.pi * self.radius ** 2


class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

现在,我们可以定义一个函数,它接受一个Shape类型的对象,并调用其area方法:

def print_area(shape):
    print(f"The area of the shape is {shape.area()}")

我们可以使用这个函数来处理不同类型的形状:

circle = Circle(5)
rectangle = Rectangle(4, 6)

print_area(circle)
print_area(rectangle)

输出为:

The area of the shape is 78.53981633974483
The area of the shape is 24

在这个例子中,print_area函数并不关心传入的对象具体是Circle还是Rectangle,只要它是Shape类的子类并且实现了area方法,就可以正确计算并输出面积。这就是多态的体现。

鸭子类型与多态

Python是一种动态类型语言,它还支持一种称为“鸭子类型”的多态形式。鸭子类型的概念是“如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子”。也就是说,Python并不关心对象的具体类型,只要对象具有所需的方法,就可以像预期的那样使用它。

例如,我们不需要显式地让一个类继承自某个特定的基类来实现多态。假设我们有一个简单的函数,它期望对象具有play方法:

def play_sound(obj):
    obj.play()

然后我们定义两个类,它们都有play方法,但没有继承关系:

class Guitar:
    def play(self):
        print("Playing guitar chords.")


class Drum:
    def play(self):
        print("Beating the drums.")

我们可以这样使用这两个类:

guitar = Guitar()
drum = Drum()

play_sound(guitar)
play_sound(drum)

输出为:

Playing guitar chords.
Beating the drums.

在这个例子中,GuitarDrum类并没有继承自同一个基类,但由于它们都有play方法,所以可以在play_sound函数中以多态的方式使用。

抽象基类与多态

在Python中,我们可以使用抽象基类(ABC)来定义一组方法的接口,然后要求子类必须实现这些方法,以确保多态的一致性。abc模块提供了定义抽象基类的工具。

抽象基类示例

假设我们定义一个抽象基类Vehicle,它有一个抽象方法move

from abc import ABC, abstractmethod


class Vehicle(ABC):
    @abstractmethod
    def move(self):
        pass

现在,我们可以创建继承自Vehicle的子类,并实现move方法:

class Car(Vehicle):
    def move(self):
        print("The car is moving on the road.")


class Boat(Vehicle):
    def move(self):
        print("The boat is sailing on the water.")

如果我们尝试创建一个没有实现move方法的子类,Python会引发一个错误:

class UnfinishedVehicle(Vehicle):
    pass

当我们尝试创建UnfinishedVehicle的实例时,会得到以下错误:

TypeError: Can't instantiate abstract class UnfinishedVehicle with abstract methods move

通过使用抽象基类,我们可以明确地定义接口,并且强制子类实现特定的方法,从而更好地实现多态。

多态与继承的综合应用

在实际编程中,继承和多态通常是结合使用的,以创建灵活、可扩展的代码结构。

综合示例:图形绘制系统

假设我们正在开发一个简单的图形绘制系统。我们有一个抽象基类Graphic,它定义了绘制图形的抽象方法draw

from abc import ABC, abstractmethod


class Graphic(ABC):
    @abstractmethod
    def draw(self):
        pass

然后,我们创建不同的图形类,如RectangleGraphicCircleGraphic,它们继承自Graphic并实现draw方法:

import math


class RectangleGraphic(Graphic):
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height

    def draw(self):
        print(f"Drawing a rectangle at ({self.x}, {self.y}) with width {self.width} and height {self.height}")


class CircleGraphic(Graphic):
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius

    def draw(self):
        print(f"Drawing a circle at ({self.x}, {self.y}) with radius {self.radius}")

我们还可以创建一个GraphicManager类,它管理一组图形并负责绘制它们:

class GraphicManager:
    def __init__(self):
        self.graphics = []

    def add_graphic(self, graphic):
        self.graphics.append(graphic)

    def draw_all(self):
        for graphic in self.graphics:
            graphic.draw()

现在,我们可以使用这些类来管理和绘制不同的图形:

manager = GraphicManager()
rect = RectangleGraphic(10, 20, 50, 30)
circle = CircleGraphic(30, 40, 15)

manager.add_graphic(rect)
manager.add_graphic(circle)

manager.draw_all()

输出为:

Drawing a rectangle at (10, 20) with width 50 and height 30
Drawing a circle at (30, 40) with radius 15

在这个示例中,通过继承Graphic类,不同的图形类实现了统一的draw接口,GraphicManager类利用多态性来处理不同类型的图形,使得代码具有良好的扩展性和维护性。

继承与多态的高级应用

元类与继承

元类是Python中用于创建类的类。虽然元类是一个高级概念,但它与继承和多态有着密切的关系。通过元类,我们可以在类定义时动态地修改类的行为,包括继承关系。

例如,假设我们有一个元类MetaClass,它可以在类定义时自动添加一个方法:

class MetaClass(type):
    def __new__(cls, name, bases, attrs):
        def new_method(self):
            print(f"I'm a method added by {cls.__name__}")
        attrs['new_method'] = new_method
        return super().__new__(cls, name, bases, attrs)

现在,我们可以使用这个元类来创建类:

class MyClass(metaclass=MetaClass):
    pass

当我们创建MyClass的实例时,它会有一个new_method

obj = MyClass()
obj.new_method()

输出为:

I'm a method added by MetaClass

在这个例子中,元类可以影响类的创建过程,包括继承关系。如果我们在元类中修改类的基类,就可以动态地改变类的继承结构,从而影响多态的行为。

多重继承与Mix - in类

Mix - in类是一种特殊的多重继承应用。Mix - in类通常是一个没有数据属性的类,它只定义了一些方法,用于为其他类提供额外的功能。通过将Mix - in类作为多重继承的一部分,我们可以为类添加功能而不改变其主要的继承层次。

例如,假设我们有一个LoggingMixIn类,它提供了日志记录功能:

import logging


class LoggingMixIn:
    def log(self, message):
        logging.info(message)

现在,我们可以创建一个类,它继承自其他类并混入LoggingMixIn类:

class MyService(LoggingMixIn):
    def perform_action(self):
        self.log("Performing an action.")
        print("Action performed.")
service = MyService()
service.perform_action()

输出为:

INFO:root:Performing an action.
Action performed.

在这个例子中,MyService类通过混入LoggingMixIn类,获得了日志记录功能,而不需要改变其主要的继承结构。这使得代码更加模块化和可复用。

动态类型与多态的优化

在Python这样的动态类型语言中,虽然多态提供了很大的灵活性,但也可能带来一些性能问题。由于Python在运行时才确定对象的类型,每次方法调用都需要进行动态查找。为了优化性能,我们可以使用typing模块来提供类型提示。

例如,假设我们有一个函数,它接受一个实现了area方法的对象:

from typing import Protocol


class ShapeProtocol(Protocol):
    def area(self) -> float:
        pass


def calculate_total_area(shapes: list[ShapeProtocol]):
    total = 0
    for shape in shapes:
        total += shape.area()
    return total

通过使用类型提示,我们可以让静态类型检查工具(如mypy)在编译时检查类型的一致性,同时也可以帮助IDE提供更好的代码补全和错误提示。虽然这并不能完全消除动态类型带来的性能开销,但可以在一定程度上提高代码的可读性和可维护性。

继承与多态在设计模式中的应用

策略模式

策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在Python中,我们可以通过继承和多态来实现策略模式。

假设我们有一个支付系统,支持不同的支付方式,如信用卡支付和PayPal支付。我们可以定义一个抽象的PaymentStrategy类,然后创建具体的支付策略类:

from abc import ABC, abstractmethod


class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount):
        pass


class CreditCardPayment(PaymentStrategy):
    def __init__(self, card_number, expiration, cvv):
        self.card_number = card_number
        self.expiration = expiration
        self.cvv = cvv

    def pay(self, amount):
        print(f"Paying ${amount} with credit card {self.card_number}")


class PayPalPayment(PaymentStrategy):
    def __init__(self, email):
        self.email = email

    def pay(self, amount):
        print(f"Paying ${amount} with PayPal account {self.email}")

然后,我们可以创建一个PaymentContext类,它使用不同的支付策略进行支付:

class PaymentContext:
    def __init__(self, strategy):
        self.strategy = strategy

    def execute_payment(self, amount):
        self.strategy.pay(amount)

现在,我们可以这样使用:

credit_card = CreditCardPayment("1234567890123456", "12/25", "123")
paypal = PayPalPayment("user@example.com")

context1 = PaymentContext(credit_card)
context1.execute_payment(100)

context2 = PaymentContext(paypal)
context2.execute_payment(200)

输出为:

Paying $100 with credit card 1234567890123456
Paying $200 with PayPal account user@example.com

在这个例子中,PaymentContext类通过多态性使用不同的支付策略,实现了策略模式。

装饰器模式

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。在Python中,我们可以使用继承和多态来实现装饰器模式的一种变体。

假设我们有一个Component类,以及一个Decorator类,它是所有具体装饰器的基类:

from abc import ABC, abstractmethod


class Component(ABC):
    @abstractmethod
    def operation(self):
        pass


class Decorator(Component, ABC):
    def __init__(self, component):
        self.component = component

    def operation(self):
        self.component.operation()

现在,我们可以创建具体的装饰器类,如ConcreteDecoratorA,它为组件添加额外的功能:

class ConcreteDecoratorA(Decorator):
    def operation(self):
        super().operation()
        print("Added behavior by ConcreteDecoratorA")

我们还可以创建具体的组件类,如ConcreteComponent

class ConcreteComponent(Component):
    def operation(self):
        print("Base operation")

现在,我们可以使用装饰器来装饰组件:

component = ConcreteComponent()
decorated_component = ConcreteDecoratorA(component)

decorated_component.operation()

输出为:

Base operation
Added behavior by ConcreteDecoratorA

在这个例子中,通过继承和多态,我们实现了装饰器模式,为对象动态地添加了新的功能。

总结

继承和多态是Python面向对象编程的核心特性,它们使得代码具有良好的扩展性、维护性和复用性。通过继承,我们可以基于现有的类创建新的类,重用父类的属性和方法,并添加自己独特的功能。多态则允许我们使用相同的操作对不同类型的对象进行处理,提高了代码的灵活性。无论是简单的继承结构,还是复杂的多重继承、抽象基类和动态类型应用,都为Python开发者提供了丰富的工具来构建高效、健壮的软件系统。同时,继承和多态在各种设计模式中的应用,进一步展示了它们在实际编程中的强大威力。在实际开发中,深入理解和熟练运用继承与多态,是成为优秀Python开发者的关键之一。