Python 类继承

在python中,类继承是面向对象的一个重要特性,他允许一个类继承另一类的属性及方法。
通俗的来说,就是龙生龙,凤生凤,老鼠的儿子会打洞。也就是儿子天生就具备父亲的一些特性。
继承能够减少重复代码,保证可维护性,以及在不修改父类的情况下通过子类添加新的功能或修改现有功能来提高代码的可拓展性

基础示例

# 创建Animal类
class Animal:
    # 初始化方法
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.species = "Animal"
        print("Animal.__init__")
    
    def speak(self):
        print(f"{self.name} is speaking.")

# 创建Dog类,继承自Animal类
class Dog(Animal):
    def __init__(self, name, age, breed):
        # 调用父类的初始化方法
        super().__init__(name, age)
        # 初始化子类属性
        self.species = "dog"
        self.breed = breed
        
    # 重写父类的speak方法
    def speak(self):
        print(f"{self.name} is barking.")

# 创建Cat类,继承自Animal类
class Cat(Animal):
    def __init__(self, name, age, color):
        # 调用父类的初始化方法
        super().__init__(name, age)
        # 初始化子类属性
        self.species = "cat"
        self.color = color
        
    # 重写父类的speak方法
    def speak(self):
        print(f"{self.name} is meowing.")


dog = Dog("Max", 3, "Labrador") 
cat = Cat("Fluffy", 2, "White")
print(f"{dog.name} is a {dog.age} years old {dog.breed} {dog.species}")
# 输出Dog对象属性 => Max is a 3 years old Labrador dog
print(f"{cat.name} is a {cat.color} {cat.species}.") 
# 输出Cat对象属性 => Fluffy is a White cat.

从代码示例中能看到,Dog和Cat类都继承了Animal类,因此Dog和Cat类都拥有Animal类的属性和方法。
在创建Dog和Cat对象时,会调用父类的初始化方法,并初始化父类的属性。再初始化子类的属性,如果子类中有新的属性或方法,则子类可以重写父类的属性和方法。

初始化时的注意点

  1. 子类的初始化方法中,若存在对父类属性的重写,要保证先调用父类的初始化方法,再赋值新的属性,不然会导致属性丢失。
  2. 若子类并不需要父类的属性,可以不进行父类的初始化。既只继承父类的方法,但这个时候要特别注意继承的父类方法中没有使用到父类中才有的属性。
  3. 如果子类没有定义多余的子属性,也可以不写子类的初始化方法。

多继承

多继承指的是一个子类同时继承多个父类。在python中,子类可以同时继承多个父类,通过多个父类的组合来实现多继承。

基础示例

# 沿用上面单继承示例中的代码
# 创建Pet类
class Pet:
    def __init__(self, weight):
        self.weight = weight
        print("Pet.__init__")

# 创建GuideDog类,继承自Dog类
class GuideDog(Dog):
    def __init__(self, name, age, weight, breed, IQ):
        super().__init__(name, age, breed)
        self.weight = weight
        self.IQ = IQ
        print("GuideDog.__init__")

# 创建Husky类,继承自Dog类和Pet类,初始化时,继承Dog类的属性和方法。
class Husky(Dog, Pet):
    def __init__(self, name, age, weight, breed):
        super().__init__(name, age, breed) # 继承了Dog类中的属性
        self.breed = 'husky'               # 覆写了Dog类中的breed属性
        self.weight = weight               # 但没有继承Pet类中的weight属性
        print("Husky.__init__")

# 创建 Fee 类,继承自GuideDog类和Husky类,初始化时,继承GuideDog类的属性和方法。
class Fee(GuideDog, Husky):
    def __init__(self, name, age, weight, breed, color, IQ):
        super().__init__(name, age, weight, breed,IQ)   # 继承了GuideDog类中的属性
        self.color = color                               # 新增 color属性
        print("Fee.__init__")

# 创建 OrangeCat 类,继承自Pet类和Cat类,初始化时,能够同时继承Cat类和Pet类的属性和方法。
class OrangeCat(Pet, Cat):
    def __init__(self, name, age, color, weight):
        Cat.__init__(self, name, age, color)  # 继承了Cat类中的属性
        Pet.__init__(self, weight)            # 继承了Pet类中的属性
        print("OrangeCat.__init__")

# 创建 CartoonCat 类,继承自Cat类
class CartoonCat(Cat):
    def __init__(self, name, age, color, gender):
        super().__init__(name, age, color)    # 继承了Cat类中的属性
        self.gender = gender                  # 新增 gender属性
        print("CartoonCharacter.__init__")

# 创建 Tom 类,继承自OrangeCat类和CartoonCat类
class Tom(OrangeCat, CartoonCat):
    def __init__(self, name, age, color, weight, gender):
        OrangeCat.__init__(self, name, age, color, weight)   # 继承了OrangeCat类中的属性
        CartoonCat.__init__(self, name, age, color, gender)  # 继承了CartoonCat类中的属性
        print("Tom.__init__")

husky = Husky("Max", 3, 50, "Labrador")
print(f"{husky.name} is a {husky.age} years old {husky.breed} {husky.species}")
# 输出 => Max is a 3 years old husky dog
# 这里能看到 Husky 这类中初始化方法里 super().__init__(*args) 初始化的是 Dog 类中的初始化方法,  
# 后续的 weight 字段虽然也传进来了,但是多继承中使用 super().__init__() 只会调用第一个父类的初始化方法  
# 根据方法解析顺序,通常是按照从左到右的顺序,因此这里虽继承了 Pet 类,但并未进行Pet类的初始化调用。

orange_cat = OrangeCat("Fluffy", 2, "Orange", 5) 
print(f"{orange_cat.name} is a {orange_cat.weight} kg {orange_cat.color} {orange_cat.species}.") 
# 输出 => Fluffy is a 5 kg Orange cat.    
# 这里能看到 OrangeCat 这类中初始化方法里采用的是调用 父类名.__init__(*args) 初始化。  
# 因此只要进行了此定义,那么就会进行按定义的顺序进行父类的初始化,若存在重名属性,则后面的属性会覆盖前面的属性。

fee = Fee("Fee", 3, 50, "Labrador", "Orange", 60)
print(f"{fee.name} is a {fee.age} years old {fee.breed} {fee.species}
      and {fee.name} weighs {fee.weight} kg has {fee.IQ} IQ")
# 输出 => TypeError: __init__() missing 1 required positional argument: 'breed'
# 注意,python中多重继承是指一个类可以从多个父类继承属性和方法。  
# 然而,当一个类从多个父类继承时,可能会出现菱形继承问题(Diamond Problem),即多重继承冲突。

tom = Tom("Tom", 3, "Orange", 5, "Male")
print(f"{tom.name} is a {tom.age} years old {tom.color} {tom.species}
      and {tom.name} weighs {tom.weight} kg has {tom.gender} gender")
# 输出 => Tom is a 3 years old Orange cat.
# 同样的 Tom 类也是多继承且具有菱形继承问题,但通过避免使用 super().__init__() 解决了菱形继承问题。

# 想要知道一个类的继承顺序,可以使用 __mro__ 属性。
print(Tom.__mro__) 
# 输出 => (<class '__main__.Tom'>, <class '__main__.OrangeCat'>, <class '__main__.Pet'>, 
# <class '__main__.CartoonCat'>, <class '__main__.Cat'>, <class '__main__.Animal'>, 
# <class 'object'>)
print(Fee.__mro__)
# 输出 => (<class '__main__.Fee'>, <class '__main__.GuideDog'>, <class '__main__.Husky'>,   
# <class '__main__.Dog'>, <class '__main__.Animal'>, <class '__main__.Pet'>, <class 'object'>>)

菱形继承问题

菱形继承问题指的是在python中,在多继承的情况下,一个类继承了两个子类,而这两个子类又继承自同一个父类。这会导致在调用父类的方法时出现歧义,因为编译器或解释器不知道应该调用哪个父类的方法。

类继承示意图

Animal

-name: str

-age: int

-species: str

+__init__(name: str, age: int)

+speak()

Dog

-breed: str

+__init__(name: str, age: int, breed: str)

+speak()

Cat

-color: str

+__init__(name: str, age: int, color: str)

+speak()

Pet

-weight: float

+__init__(weight: float)

GuideDog

-weight: float

-IQ: int

+__init__(name: str, age: int, weight: float, breed: str, IQ: int)

Husky

-breed: str

-weight: float

+__init__(name: str, age: int, weight: float, breed: str)

OrangeCat

-color: str

-weight: float

+__init__(name: str, age: int, color: str, weight: float)

CartoonCat

-gender: str

+__init__(name: str, age: int, color: str, gender: str)

Tom

-color: str

-weight: float

-gender: str

+__init__(name: str, age: int, color: str, weight: float, gender: str)

Fee

-color: str

-IQ: int

+__init__(name: str, age: int, weight: float, breed: str, color: str, IQ: int)

在我们的例子中,我们定义了十个类,其中 Dog 和 Cat 都继承自 Animal 类,而 GuideDog 和 Husky 都继承自 Dog 类, OrangeCat 和 CartoonCat 都继承自 Cat 类。我们在上述示例中创建Fee的实例对象时发生了菱形继承问题,因为Fee同时继承了GuideDog和Husky两个父类,而GuideDog和Husky又同时继承了Dog类,导致在调用父类的方法时出现歧义。但我们使用显式调用父类的方法时,成功创建了Tom对象以避免菱形继承问题。

类继承中的方法解析顺序

在Python中,类的继承关系是通过类的继承顺序来决定的。当一个类继承自多个父类时,Python会按照从左到右的顺序进行搜索,直到找到所需的方法或属性为止。这称为“方法解析顺序”或“MRO”。

MRO的计算规则 —— C3 Linearization

MRO是遵循C3线性化算法的,这个算法确保了每个类的MRO都是单调的。既如果一个类出现在另一个类之前,那么在所有父类中它也应该保持在这个位置之前。同时,他也保证了子类总是优先于父类被检查。
C3线性化的规则:
1.单调性: 如果一个类B出现在另一个类A之前,那么在所有父类中B也应该保持在A之前。
2.局部优先:子类总是优先于父类被检查。
3.保持父类的顺序:多个父类的相对顺序应该保持不变。
计算步骤:
给定一个类C及其父类列表[P1, P2, …, PN],C3线性化的过程如下:
a.初始化:
       将类C本身作为线性化序列的第一个元素。
       获取每个父类的MRO,并将它们放入一个列表中。
b.合并父类MRO:
       从左到右遍历父类列表,依次尝试将每个父类的MRO合并到当前线性化序列中。
       每次选择第一个可以安全添加到线性化序列中的类(即该类不在后续待处理类的前面)。
最终得到的线性化序列即为类C的MRO。沿用上面的例子,查看 TOM 类的MRO:

print(Tom.__mro__) 
# 输出 => (<class '__main__.Tom'>, <class '__main__.OrangeCat'>, <class '__main__.Pet'>,
# <class '__main__.CartoonCat'>, <class '__main__.Cat'>,  <class '__main__.Animal'>, 
# <class 'object'>)
  • Tom 首先出现,既作为线性化序列的第一个元素。
  • 然后是父类 OrangeCat 和 CartoonCat,
    OrangeCat 的 MRO 是 [OrangeCat, Pet, Cat, Animal, object],
    CartoonCat 的 MRO 是 [CartoonCat, Cat, Animal, object]。
  • 合并父类 MRO,从左到右依次选择第一个可以安全添加到线性化序列中的类,即 Tom, OrangeCat, Pet, CartoonCat, Cat, Animal, object。
  • 不能被子类继承的特殊情况:

    1. 私有方法:私有方法(以双下划线开头)不能被继承,因为私有方法只能在类的内部使用,不能被外部访问。
    2. 私有属性:私有属性(以双下划线开头)不能被继承,因为私有属性只能在类的内部使用,不能被外部访问。

    作者:每天减 1/5kg

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python 类继承

    发表回复