Python的继承

前言

在面向对象编程(OOP)中,继承是一个强大且重要的概念。它允许我们基于现有类创建新类,从而实现代码的重用和结构化。本文将深入探讨Python中的继承,包括其基本概念、使用方法以及高级技巧。

什么是继承

继承是一种机制,通过它,一个类(子类)可以继承另一个类(父类)的属性和方法。子类不仅可以使用父类的所有功能,还可以增加新的功能或者重写父类的功能。

基本概念

在Python中,继承通过类定义中的括号来实现,Python默认继承Object

class Parent:
    # 父类的属性和方法

class Child(Parent):
    # 子类的属性和方法

示例

我们通过一个简单的例子,来看一下继承的使用方法

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

    def speak(self):
        pass
    
    def explain(self):
        return f"{self.name}:I am a animal"

class Dog(Animal):
    def speak(self):
        return f"{self.name} says wow!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says miao!"
dog = Dog("xiaohei")
cat = Cat("dabai")
print(dog.speak())  # 输出: xiaohei says wow!
print(cat.speak())  # 输出: dabai says miao!
print(dog.explain())  # 输出: xiaohei:I am a animal
print(cat.explain())  # 输出: dabai:I am a animal

上面代码中Dog和Cat都继承自Animal,重写了speak,但也可以直接使用父类的explain函数。

super()函数

在子类中调用父类的方法,可以使用super()函数。这对于扩展或重写父类的方法特别有用。

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

    def speak(self):
        return "Animal sound"

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

    def speak(self):
        return super().speak() + " wow!"

dog = Dog("xiaohei")
print(dog.speak())  # 输出: Animal sound wow!

多重继承

Python还支持多重继承,即一个类可以继承多个父类。多重继承是一个难点,有些语言不支持多重继承,虽然这在某些情况下非常有用,但也需要谨慎使用,以避免复杂性和潜在的冲突。

class Parent1:
    def method1(self):
        print("Parent1 method1")

class Parent2:
    def method2(self):
        print("Parent2 method2")

class Child(Parent1, Parent2):
    pass

child = Child()
child.method1()  # 输出: Parent1 method1
child.method2()  # 输出: Parent2 method2

多重继承代码看起来确实很简单,和单继承差不多,但实际应用中可能会更为复杂。我们再来看一个更为复杂的多继承的示例(代码引用《流畅的Python》第二版)

class Root:
    def ping(self):
        print(f'{self}.ping() in Root')
    
    def pong(self):
        print(f'{self}.pong() in Root')
        
    def __repr__(self):
        cls_name = type(self).__name__
        return f'<instance of {cls_name}>'
        
class A(Root):
    def ping(self):
        print(f'{self}.ping() in A')
        super().ping()
    
    def pong(self):
        print(f'{self}.pong() in A')
        super().pong()
        
class B(Root):
    def ping(self):
        print(f'{self}.ping() in B')
        super().ping()
    
    def pong(self):
        print(f'{self}.pong() in B')
        
class Leaf(A,B):
    def ping(self):
        print(f'{self}.ping() in Leaf')
        super().ping()
        
leaf1 = Leaf()
print('---------ping---------')
leaf1.ping()
print('---------pong---------')
leaf1.pong()

上面首先定义了基类Root,然后A,B继承了Root,并重写了基类的方法,唯一不同的是,在B的pong方法中,没有调用super(),Leaf又继承了A和B,重写了ping,运行代码,看一下结果

---------ping---------
<instance of Leaf>.ping() in Leaf
<instance of Leaf>.ping() in A
<instance of Leaf>.ping() in B
<instance of Leaf>.ping() in Root
---------pong---------
<instance of Leaf>.pong() in A
<instance of Leaf>.pong() in B

Leaf对象调用ping()方法时,调用顺序是Leaf -> A -> B -> Root,而调用pong()时,调用顺序是 Leaf -> A -> B,没有调用Root,如果将Leaf 定义为

class Leaf(B,A):
    def ping(self):
        print(f'{self}.ping() in Leaf')
        super().ping()

调用pong(),你会发现,只会答应 <instance of Leaf>.pong() in B。也就是 Leaf -> B
不知你发现一点端倪没有,在B的pong()方法中,没有调用super(), 导致调用结果大不相同,前面说过子类可以通过super()调用父类方法,但在多重继承中,调用super()的方法叫协作方法,利用协作方法可以实现协作多重继承。我的理解就是,在多重继承中,是否调用super(),决定会不会继续调用下一个基类的方法,从上面的示例中,我们可以发现子类执行基类方法时,在从括号的左到右的顺序执行的
上面示例中,A的ping,pong和B的ping都是协作方法,B的pong不是,所以当Leaf的对象执行pong时,执行到B这里后,就戛然而止了。可能会有点绕,但自己复现以下代码,多执行几次,调换一下Leaf中继承的顺序,应该会有一定程度的理解。

混入类

混入类在多重继承中会连同其他类一起被子类化混入类不能作为具
体类的唯一基类
,因为混入类不为具体对象提供全部功能,而是增加
或定制子类或同级类的行为。
上面提到super()的作用,就是为了接下来要谈论的混入类,下面来看一下代码

class TestMixin:
    def speak(self):
        print(f'{self}.speak() in TestMixin')
        super().speak()
        
class A:
    def speak(self):
        print(f'{self}.speak() in A')
       
        
class Leaf(TestMixin,A):
    def speak(self):
        print(f'{self}.speak() in Leaf')
        super().speak()
        
leaf1 = Leaf()
leaf1.speak()

在没有运行代码之前,你猜测结果是什么呢?我最开始认为代码会报错,因为TestMixin中,super默认是Object,应该没有实现speak()方法,应该要报错呀,但实际结果出乎意料

<__main__.Leaf object at 0x000001EDA24AEDD0>.speak() in Leaf
<__main__.Leaf object at 0x000001EDA24AEDD0>.speak() in TestMinin
<__main__.Leaf object at 0x000001EDA24AEDD0>.speak() in A

Leaf调用执行顺序为 Leaf -> TestMixin -> A ,不仅没有报错,还正常调用了基类方法,同时要注意到,混入类不能独立使用,也不能单独被继承的。
一般情况下,使用混入类都要求

  • 避免状态共享: 混入类通常不应该有共享状态,避免不同类实例之间的状态混淆。
  • 单一职责: 每个汇入类应该只提供一个单一的功能,保持简洁和易于组合。
    在Django和Tkinter中,大量运用了多重继承和混入类的使用,这里不展开了,有兴趣的可以去查阅一下相关资料。

这里介绍多重继承和混入类,主要是关注**super()**在其中的作用,简单来说,在多重继承中,super()就像一个传递者一样,让子类能依序调用多个基类的方法。

继承的高级技巧

抽象基类

抽象基类定义了一组方法,子类必须实现这些方法。和一些语言里面的接口其实是一个意思Python的abc模块提供了抽象基类的支持。

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def eat(self):
        print('eat')
 
    def speak(self):
        return "wow!"

dog = Dog()
print(dog.speak())  # 输出: wow!

实现上和继承是一样的,只是在父类方法中使用了@abstractmethod,但子类中必须要实现这个方法,不重写方法,就会报错,你可以尝试一下,我举得抽象基类就是python的接口表现形式

方法解析顺序(MRO)

在多重继承中,方法解析顺序(MRO)决定了在调用方法时搜索的顺序。可以使用ClassName.mro()查看类的MRO。多重继承和混入类的代码中,你们一定也注意到了子类执行父类时的顺序,Python中提供ClassName.mro(),可以查看顺序。我们打印一下多重继承示例中,Leaf类的mro

print(Leaf.mro())
# 输出结果:[<class '__main__.Leaf'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Root'>, <class 'object'>]

可以看到输出是 Leaf -> A -> B -> Root -> object,子类搜索基类的顺序从左到右,从下到上。

总结

继承是Python面向对象编程中的一个核心概念,它使得代码更加模块化和可重用。通过理解和正确使用继承,我们可以创建更加复杂和功能丰富的程序。同时,注意使用super()函数、抽象基类和理解方法解析顺序,可以让我们在处理复杂的继承关系时更加得心应手。多重继承和混入类是一个难点,不仅难以理解,还容易造成不必要的错误,在实际开发中慎用。其实绝大多数情况下,开发者几乎不会用到这两个功能了,但也有必要了解一下

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值