继承

继承概述

  • 可以通过继承来创建一个新类(子类),该类继承了另一个类(父类)的属性和方法,并可以在其基础上进行扩展或修改。
  • 例如:猫类和狗类可以继承自动物类,动物类是父类,猫类和狗类是子类;
  • 从父类继承,就可以直接拥有父类的属性和方法。这样可以减少代码量,多复用。子类也可以定义自己的属性和方法。
  • 继承通过在类定义时将父类的名称放在子类名称后面的括号中来实现

继承的用途:在子类上实现对基类的增强,实现多态

以下是一个简单的示例,演示了如何在Python中使用继承:

## 定义一个父类
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        print(f"{self.name} makes a sound.")

## 定义一个子类,继承自Animal
class Dog(Animal):
    def speak(self):
        print(f"{self.name} says Woof!")

## 定义另一个子类,也继承自Animal
class Cat(Animal):
    def speak(self):
        print(f"{self.name} says Meow!")

## 创建父类对象
animal = Animal("Generic Animal")
animal.speak()

## 创建子类对象
dog = Dog("Buddy")
dog.speak()

cat = Cat("Whiskers")
cat.speak()

在这个示例中,我们定义了一个名为Animal的父类,它具有一个名为speak()的方法。然后我们定义了两个子类DogCat,它们分别继承了Animal类,并且重写了speak()方法以表达不同的声音。最后,我们创建了父类和子类的对象,并调用它们的speak()方法。

继承使得代码更加模块化、可重用和易于维护,因为它允许您在不重复编写相似代码的情况下创建新的类,并且可以根据需要对继承的行为进行定制化修改。

根基类 object

Python3 中,所有的类都是继承自一个名为object的基类。object是Python中所有类的顶级基类,它定义了一些基本的行为和方法,所有其他类都直接或间接地继承了这些特性。尽管在定义类时通常不需要显式地指定继承自object,但如果不继承其他类,Python会默认将其视为继承自object

object类提供了一些常用的方法和属性,包括:

  • __init__(self[, ...]): 初始化方法,用于创建对象并设置初始状态。
  • __new__(cls[, ...]): 用于创建对象的实例,通常情况下不需要重写它。
  • __del__(self): 对象销毁时调用的方法,在对象被垃圾回收之前执行清理工作。
  • __repr__(self): 返回对象的“官方”字符串表示,通常用于调试和日志记录。
  • __str__(self): 返回对象的“非官方”字符串表示,通常用于用户友好的输出。
  • __eq__(self, other): 定义对象之间的相等性比较。
  • __hash__(self): 返回对象的哈希值,通常与__eq__一起使用。
  • __getattr__(self, name): 获取对象的属性,当属性不存在时调用。
  • __setattr__(self, name, value): 设置对象的属性,当属性被赋值时调用。
  • __delattr__(self, name): 删除对象的属性,当属性被删除时调用。

除了上述方法之外,object类还定义了一些特殊的方法,用于支持对象的比较、布尔测试、迭代等功能。通过继承自object,所有的Python类都具有这些方法的默认实现。

总之,object类是Python中所有类的根基类,它定义了一些基本的行为和方法,为Python的面向对象编程提供了基础。虽然在定义类时通常不需要显式地指定继承自object,但它是Python中的一个重要概念,理解它有助于更好地理解Python中的类和对象。

继承常用方法与特殊属性

__bases__ 返回父类

__bases____base__,建议使用 __bases__

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

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


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


class Cat(Animal):
    def speak(self):
        print(f"{self.name} says Meow!")


print(Dog.__bases__) # (<class '__main__.Animal'>,)
print(Cat.__bases__) # (<class '__main__.Animal'>,)
print(Dog.__base__) # <class '__main__.Animal'>,相当于Dog.__bases__[0],即元组的第一项
print(Cat.__base__) # <class '__main__.Animal'>
print(Animal.__bases__) # (<class 'object'>,)

__subclasses__() 返回子类

__subclasses__(),返回列表,因为一个父类可能有n多个子类

1

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

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


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


class Cat(Animal):
    def speak(self):
        print(f"{self.name} says Meow!")


print(Animal.__subclasses__()) # [<class '__main__.Dog'>, <class '__main__.Cat'>]

2

print(bool.__bases__) # (<class 'int'>,)
print(int.__subclasses__()) # [<class 'bool'>, <enum 'IntEnum'>, <enum 'IntFlag'>, <class 'sre_constants._NamedIntConstant'>]

__mro_ 返回方法解析顺序

__mro__mro() 都与方法解析顺序(Method Resolution Order,MRO)有关,用于确定在多重继承情况下,调用方法时的查找顺序。

  • __mro__:是一个特殊的属性,用于获取类的方法解析顺序。它返回一个元组,按照继承关系从当前类到其基类,列出了方法的搜索顺序。这个元组的顺序反映了方法解析的顺序,即在多重继承时,Python 在查找方法时会按照这个顺序进行搜索。

  • mro():是一个类方法,用于获取类的方法解析顺序。调用这个方法会返回一个元组,其中包含了与__mro__相同的信息,即类的方法解析顺序。

例如:

class A:
    def foo(self):
        print("A foo")

class B(A):
    def foo(self):
        print("B foo")

class C(A):
    def foo(self):
        print("C foo")

class D(B, C):
    pass

## 获取类D的方法解析顺序
print(D.__mro__)  # 输出: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(D.mro())    # 输出: [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

在上面的示例中,类 D 继承自 BC,而 BC 都继承自 A。因此,根据方法解析顺序,当调用 D 类的方法时,会按照 D -> B -> C -> A -> object 的顺序进行查找。

继承本质

生产中不要这样写,以下代码仅作为探索python类继承本质:

class Animal:
    __COUNT = 100
    HEIGHT = 0

    def __init__(self, age, weight, height):
        self.__COUNT += 1
        self.age = age
        self.__weight = weight
        self.HEIGHT = height

    def eat(self):
        print('{} eat'.format(self.__class__.__name__))

    def __getweight(self):
        print(self.__weight)

    @classmethod
    def showcount1(cls):
        print(cls)
        print(cls.__dict__)
        print(cls.__COUNT)

    @classmethod
    def __showcount2(cls):
        print(cls.__COUNT)

    def showcount3(self):
        print(self.__COUNT)


class Cat(Animal):
    NAME = 'CAT'
    __COUNT = 200


c = Cat(3, 5, 15)
c.eat() # Cat eat,首先找实例字典有没有eat()属性,没有则找自己的Cat类,Cat类也没有则找父类Animal。
print(c.HEIGHT) # 15
c.showcount1()
'''
<class '__main__.Cat'>
{'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}
100 # 因为实际访问的是_Animal__COUNT
'''
c.showcount3() # 101
print(c.__dict__) # {'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15}(注意!这是c的实例字典)
print(c._Cat__COUNT) # 200,实例字典没有,就朝自己的类Cat要,因此返回200
print(c._Animal__COUNT) # 101,实例字典有,就直接返回实例字典的结果
print(c.NAME) # CAT,实例字典没有,就朝自己的类Cat要,因此返回CAT

从父类继承,自己没有的,就可以到父类中找。

私有的都是不可以访问的,但是本质上依然是改了名称放在这个属性所在类或实例的__dict__中。知道这个新名称就可以直接找到这个隐藏的变量,这是个黑魔法技巧,慎用。

总结

继承时:

  • 公有成员、子类、实例都可以随意访问;
  • 私有成员被隐藏,子类和实例不可直接访问,但私有变量所在的类内的方法中可以访问这个私有变量。
  • 私有的东西不会继承

实例属性查找顺序:

  • 实例的__dict__ -> 类__dict__ -> 如果有继承 -> 父类dict

    • 到最后还未找到则抛异常,先找到则立即返回结果。
  • 示例代码:

    • class A:
          a = 100
      
      x = A()
      print(x.a) # 100
      
      class B:
          a = 100
          def __init__(self):
              self.a = 200
      
      y = B()
      print(y.a) # 200
      print(y.__dict__) # {'a': 200}

完全覆盖 & 父类基础上覆盖

在 Python 中,完全覆盖(Complete Overriding)和基于父类的覆盖(Overriding Based on Parent Class)是指在子类中覆盖父类的方法的两种方式。它们的实现方式略有不同。

  1. 完全覆盖(Complete Overriding): 完全覆盖是指在子类中重新定义一个与父类完全相同名称的方法,并提供新的实现逻辑。在这种情况下,子类完全取代了父类的方法,父类的方法对子类来说不可见。

    class Parent:
        def some_method(self):
            print("Parent's method")
    
    class Child(Parent):
        def some_method(self):
            print("Child's method")
    
    obj = Child()
    obj.some_method()  # 输出: Child's method
  2. 基于父类的覆盖(Overriding Based on Parent Class): 基于父类的覆盖是指在子类中重新定义一个与父类相同名称的方法,并在新的实现逻辑中调用父类的方法,以便在子类中扩展或修改父类方法的行为。

    class Parent:
        def some_method(self):
            print("Parent's method")
    
    class Child(Parent):
        def some_method(self):
            super().some_method()  # 调用父类方法
            print("Child's method")
    
    obj = Child()
    obj.some_method()
    '''
    输出:
    Parent's method
    Child's method
    '''

    在这个例子中,super().some_method() 调用了父类的 some_method 方法,这样就可以在子类的方法中利用父类方法的功能,同时在其基础上进行扩展或修改。

这两种方式都可以根据需要进行选择,如果想要完全替换父类方法的行为,则使用完全覆盖;如果想要在父类方法的基础上进行扩展或修改,则使用基于父类的覆盖,并在子类方法中使用 super() 调用父类方法。

super()

super() 是一个内置函数,用于获取当前类的父类,并调用父类的方法。它提供了一种方便的方式来在子类中调用父类的方法,特别是在多重继承的情况下。在 Python 3 中,super() 最常用的形式是不带参数的 super(),它会自动获取当前类和实例,并在调用时正确地将参数传递给父类的方法。

super() 的用法示例如下:

class Parent:
    def some_method(self):
        print("Parent's method")

class Child(Parent):
    def some_method(self):
        print("Child's method")
        super().some_method()  # 调用父类方法

obj = Child()
obj.some_method()

在上面的示例中,super().some_method() 调用了父类 Parentsome_method 方法,而不需要显式指定父类的名称。

super() 的参数通常不需要手动指定,它会自动根据当前类和实例来确定要调用的父类方法。但在一些特殊情况下,如果子类中的方法签名与父类方法不同,可能需要手动指定父类和实例作为参数,如 super(Child, self).some_method()。这种用法一般用于解决方法签名不匹配的问题。

super() 应该在子类中的方法中使用,用于调用父类的方法。它的定义位置在子类中的方法内部,通常在方法中需要调用父类方法的地方使用。

示例

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

    def say_hello(self):
        print(f"Hello, my name is {self.name}")

class ChildClass(ParentClass):
    def __init__(self, name, age):
        # 调用父类的构造函数
        super().__init__(name)
        self.age = age

    def say_hello(self):
        # 调用父类的 say_hello 方法
        super().say_hello()
        print(f"I'm {self.age} years old")


## 创建子类的实例
child = ChildClass("Alice", 25)

## 调用子类的方法
child.say_hello()
'''
输出:
Hello, my name is Alice
I'm 25 years old
'''

在上述示例中,我们有一个父类 ParentClass 和一个子类 ChildClass

  • 在子类的 __init__ 方法中,使用 super().__init__(name) 调用父类的构造函数,将父类的属性 name 初始化。
  • 在子类的 say_hello 方法中,使用 super().say_hello() 调用父类的 say_hello 方法,执行父类的打印逻辑。

这样,通过使用 super(),子类可以继承父类的方法和属性,并在需要时进行扩展和修改。super() 提供了一种统一的方式来访问父类的方法,避免了在子类中显式引用父类的名称,提高了代码的可维护性和可读性。

示例:对于类方法与静态类方法

class A:
    @classmethod
    def clsmtd(cls):
        print('A classmethod')

    @staticmethod
    def stmtd():
        print('A static method')

class B(A):
    @classmethod
    def clsmtd(cls):
        super().clsmtd()
        print('B classmethod')

    @staticmethod
    def stmtd():
        #super().stmtd() # RuntimeError: super(): no arguments
        A.stmtd()
        print('B static method')


b = B()
b.clsmtd()
'''
A classmethod
B classmethod
'''
print('~~~')
b.stmtd()
'''
A static method
B static method
'''

静态方法和类方法,是特殊的方法,也是类属性,这些方法都可以覆盖,原理都一样,属性字典的搜索顺序

继承时使用 __init__ 初始化

示例1

class A:
    def __init__(self, a, d=10):
        self.a = a
        self.d = d

class B(A):
    def __init__(self, b, c):
        super().__init__(b + c, b - c)
        #A.__init__(self, b + c, b - c) # 与使用super()等价
        self.b = b
        self.c = c
    def printvalues(self):
        print(self.b)
        print(self.c)
        print(self.a)
        print(self.d)

b = B(200, 300)
print(b.__dict__) # {'a': 500, 'd': -100, 'b': 200, 'c': 300}
b.printvalues()
'''
200
300
500
-100
'''

在上面的例子中,如果:

  • A、B 都没有 __init__,用 object 的;
  • A 有 __init__,B 没有,用 A 的;
  • A、B 都有 __init__,用 B 的。

示例2

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

class Cat(Animal):
    def __init__(self, name, age=11):
        super().__init__(name, age)
        self.age += 1

c = Cat('tom')
print(c.age) # 12

这段代码的执行结果是 12。现在让我来解释一下为什么会是这样。

  1. Animal 类有一个构造函数 __init__,它接受两个参数 nameage,其中 age 的默认值是 10。当创建一个 Animal 的实例时,可以传入 name 和可选的 age 参数。

  2. Cat 类是 Animal 类的子类。它也有一个构造函数 __init__,它接受 name 和可选的 age 参数。但是它覆盖了父类的 __init__ 方法。在子类的构造函数中,首先调用了父类 Animal 的构造函数,使用 super().__init__(name, age) 这行代码。这样做的目的是确保父类的构造函数被正确地调用,以便子类能够继承父类的属性和行为。同时,也允许我们在子类的构造函数中进行额外的初始化工作。

  3. Cat 类的构造函数中,对年龄 age 进行了额外的操作,即 self.age += 1。这意味着每次创建一个 Cat 的实例时,其年龄都会增加 1

  4. 创建了一个名为 cCat 类的实例,传入了名字 'tom',但没有传入年龄参数。因此,它的年龄将默认为 11Cat 类中默认值为 11,而不是 Animal 类的默认值 10)。然后由于 Cat 类的构造函数中有 self.age += 1 这一行,所以 age 又增加了 1,因此最终输出的年龄为 12

使用 super() 的好处在于它可以确保子类的构造函数中包含了父类的初始化逻辑,使得代码更加清晰、易读,并且使得继承链更加灵活,以便在后续对类的层级结构进行调整时,代码仍然能够正常工作。

示例3

class A:
    def __init__(self, age):
        self.__age = age
    def show(self):
        print(self.__age)

class B(A):
    def __init__(self, age):
        super().__init__(age)
        self.__age = age + 1

b = B(10)
b.show() # 10

print(b.__dict__) # {'_A__age': 10, '_B__age': 11}

上例中打印10,原因看 __dict__ 就知道了。因为父类 Ashow 方法中,__age 会被解释为 _A__age,因此显示的是10,而不是11。

这样的设计不好,B 的实例 b 应该显示自己的属性值更好。

解决方法:一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其他类的方法,即使是父类或者派生类的方法

单继承

Python 中的单继承是指一个类只能继承自一个父类。这意味着一个子类只能直接继承自一个父类,而不能同时继承多个父类。这种单继承的模式在 Python 中是默认的。

例如,下面是一个简单的示例,展示了单继承的概念:

class Animal:
    def speak(self):
        print("Animal speaks")

class Dog(Animal):
    def bark(self):
        print("Dog barks")

## 创建 Dog 类的实例
dog = Dog()

## 调用继承自父类 Animal 的方法
dog.speak()  # 输出: Animal speaks

## 调用子类自己定义的方法
dog.bark()   # 输出: Dog barks

在这个例子中,Dog 类继承自 Animal 类。因此,Dog 类可以使用 Animal 类中定义的方法,比如 speak() 方法。同时,Dog 类也可以定义自己的方法,比如 bark() 方法。

单继承的模式使得类之间的关系更加简单清晰,而且避免了多继承可能带来的复杂性和混淆。当然,在某些情况下,如果需要多个不同父类的功能,可以使用 Mixin 或者接口的方式来实现类似多继承的效果。

抽象基类

描述1

在 Python 中,抽象基类(Abstract Base Classes,简称 ABCs)是一种用于定义接口规范的机制。它们允许你定义一个抽象类,其中包含一些方法的签名,但是没有具体的实现。其他类可以通过继承这些抽象基类来实现这些方法,从而确保一致的接口和行为。

为什么使用抽象基类?

  1. 约束子类行为:抽象基类定义了子类必须实现的方法,确保了在继承关系中的一致性和可预测性。

  2. 提供接口规范:通过抽象基类,可以明确地定义一个类应该具有哪些方法,帮助其他开发者理解你的代码设计。

  3. 提高代码可读性:通过使用抽象基类,可以更清晰地表达类之间的关系和约束。

如何定义抽象基类?

在 Python 中,你可以使用 abc 模块来定义抽象基类。通常,你需要继承 ABC 基类,并使用 @abstractmethod 装饰器来标记需要子类实现的方法。

from abc import ABC, abstractmethod

class MyBaseClass(ABC):
    @abstractmethod
    def my_abstract_method(self):
        pass

    @abstractmethod
    def another_abstract_method(self):
        pass

如何使用抽象基类?

其他类可以通过继承抽象基类来实现其中定义的抽象方法。如果一个类没有实现抽象基类中的所有抽象方法,则无法被实例化。

class MyConcreteClass(MyBaseClass):
    def my_abstract_method(self):
        # 具体实现

    def another_abstract_method(self):
        # 具体实现

注意事项:

  1. 确保实现所有抽象方法:子类必须实现抽象基类中定义的所有抽象方法,否则会引发 TypeError

  2. 可以有具体方法:抽象基类中不一定所有方法都需要是抽象的,可以包含具体的方法实现。

  3. 可以多重继承:一个类可以继承多个抽象基类,但是如果有重复的抽象方法,只需要实现一次。

  4. 可以使用 isinstance 来检查类型:抽象基类可以用来检查对象是否实现了某些特定的方法。

  5. 尽量避免滥用:抽象基类应该被用来定义接口规范,而不是强制要求继承它们。

抽象基类是一种强大的工具,可以帮助你更好地组织和设计代码结构,提高代码的可读性和可维护性。

描述2

抽象基类(Abstract Base Classes,ABCs)是 Python 中用于定义接口和规范的一种机制。它们提供了一种方式来指定一个类应该实现哪些方法,但并不会强制要求这些方法必须在类中直接实现。相反,它们定义了一组必须在子类中实现的方法的契约。这种方式可以帮助确保在编写大型代码库时,不同的类之间能够互相兼容和交互。

在 Python 中,ABCs 是通过 abc 模块提供的。要创建一个抽象基类,你可以继承 abc.ABC 或者直接使用 abc.ABCMeta 元类。然后通过在方法上使用 @abstractmethod 装饰器来标记抽象方法,这些方法在子类中必须被实现。

下面是一个简单的示例:

from abc import ABC, abstractmethod

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

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

## 无法实例化 Animal 类,因为它是抽象的
## animal = Animal()  # 会抛出 TypeError

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

cat = Cat()
print(cat.speak())  # 输出: Meow!

在这个示例中,Animal 是一个抽象基类,它定义了一个抽象方法 speakDogCat 类分别是 Animal 的子类,它们必须实现 speak 方法才能被实例化。如果尝试实例化 Animal 类,会抛出 TypeError,因为抽象类不能直接实例化。

抽象基类的另一个用途是确保子类实现了某些特定的行为或属性。这对于编写库或框架时尤其有用,因为它可以提供一种约定,确保用户在实现自己的子类时不会遗漏重要的细节。

示例1

class Document:
    def __init__(self, content):
        self.content = content

class Word(Document): pass

class Pdf(Document): pass

多继承 & Mixin

  • 假设:Document 类是其他所有文档类的抽象基类;Word、Pdf 类是 Document 的子类。
  • 需求:为 Document 子类提供打印能力。

思路1:在 Document 类中提供 print 方法

class Document: # 抽象基类,不推荐对抽象基类实例化
    def __init__(self, content):
        self.content = content
    def print(self): # 未实现的方法,抽象方法,规范子类行为
        # 基类不实现,我不知道子类们如何实现自己的格式打印,但是我知道你们都得有打印功能,子类中需覆盖重写
        raise NotImplementedError # 抛异常,未实现异常,以避免对抽象基类实例化

class Word(Document): pass # 其他功能略去

class Pdf(Document): pass # 其他功能略去

PS:可能并不是所有 Document 的子类都需要 print 方法。所以,从这个角度出发,上面的基类 Document 设计有点问题。

解释

这个设计尝试在抽象基类 Document 中提供一个打印方法,但是该方法没有具体的实现,而是抛出了一个 NotImplementedError 异常,这样的设计表明任何继承自 Document 的子类都必须实现自己的打印方法。

这样的设计方式有几个优点和一些注意事项:

优点:

  1. 统一接口: 所有继承自 Document 的子类都必须实现 print 方法,这样可以保证统一的接口,使得代码更加清晰和易于理解。
  2. 避免误用: 如果某个子类忘记实现 print 方法,调用时会抛出 NotImplementedError 异常,提醒开发者需要实现该方法。

注意事项:

  1. 抽象基类设计不当: 如你所指出的,不是所有的子类都需要具备打印方法,因此 Document 类的设计可能不够灵活,应该根据实际情况考虑是否将 print 方法移到更具体的子类中。
  2. 可能增加子类的负担: 如果 Document 的子类很多,且大部分都需要实现自己的打印方法,那么这样的设计可能会增加一些额外的工作量。

总的来说,这种设计是一种典型的模板方法模式,通过在基类中定义算法框架,并在子类中实现具体细节,以提供一种通用的解决方案。

思路2:在需要打印的子类上增加 print 方法

class Document: # 第三方库,不允许修改
    def __init__(self, content):
        self.content = content
class Word(Document): pass # 第三方库,不允许修改
class Pdf(Document): pass # 第三方库,不允许修改

## 单继承
class PrintableWord(Word):
    def print(self):
        print(self.content)

print(PrintableWord.__dict__) # {'__module__': '__main__', 'print': <function PrintableWord.print at 0x000001E0657BA680>, '__doc__': None}
print(PrintableWord.mro()) # [<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]

pw = PrintableWord('test string')
pw.print() # test string

看似不错,如果还需要提供其他能力,如何继承?

  • 例如,如果该类用于网络,还应具备序列化的能力,在类上就应该实现序列化。
  • 可序列化还可能分为使用 pickle、json、messagepack 等。

这个时候发现,为了增加一种能力,就要增加一次继承,类可能太多了,继承的方式不是很好了。

可提供的功能太多,A类需要某几样功能,B类需要另几样功能,它们需要的是多个功能的自由组合,而通过继承实现会很繁琐。

解释

这种设计思路是将具体的功能(比如打印能力)直接放在需要的子类中实现,而不是在抽象基类中定义一个抽象方法。这样做的好处是可以根据需要自由组合不同的功能,但是也存在一些问题:

  1. 继承链的复杂性增加: 如果需要为多个类提供相同的功能,就需要为每个类都创建一个新的子类来实现这个功能,这会导致继承链变得更加复杂,难以管理和理解。

  2. 类的数量增加: 随着功能的增加,类的数量也会增加,可能会导致类的数量过多,增加代码维护的难度。

  3. 类的耦合性增加: 如果某个类需要多个功能,就需要继承多个不同的子类,这样会增加类与功能之间的耦合性,降低了代码的灵活性和可维护性。

综上所述,虽然这种设计思路可以灵活地组合不同的功能,但是在功能增多、需求变更等情况下,会导致代码变得难以维护。在设计类的时候,应该考虑如何通过更合适的方式来组织代码,以提高代码的可读性、可维护性和可扩展性。

思路3:使用装饰器

用装饰器增强一个类,把功能给类附加上去,哪个类需要,就装饰它。

def printable(cls):
    def _print(self):
        print(self.content, '装饰器')
    cls.print = _print
    return cls

class Document: # 第三方库,不允许修改
    def __init__(self, content):
        self.content = content
class Word(Document): pass # 第三方库,不允许修改
class Pdf(Document): pass # 第三方库,不允许修改

@printable # 先继承,后装饰
class PrintableWord(Word): pass
print(PrintableWord.__dict__) # {'__module__': '__main__', '__doc__': None, 'print': <function printable.<locals>._print at 0x000001EAE3F09A20>}
print(PrintableWord.mro()) # [<class '__main__.PrintableWord'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]

pw = PrintableWord('test string')
pw.print() # test string 装饰器

@printable
class PrintablePdf(Pdf): pass

优点:简单方便,在需要的地方动态增加,直接使用装饰器,可以为类灵活的增加功能。

解释

思路3使用了装饰器来增强类的功能,通过在需要增强功能的类上应用装饰器,可以动态地为这些类添加新的方法或属性。

在这个例子中,printable 装饰器接受一个类作为参数,并为该类动态添加了一个名为 _print 的方法,用于打印类的内容。然后,装饰器返回被增强后的类。

在使用装饰器的时候,可以直接在需要增强功能的类的定义之前使用 @printable 语法糖来应用装饰器,从而实现对该类的增强。

这种方式的优点是:

  1. 灵活性: 可以根据需要为任意类动态地增加功能,而无需修改类的定义,从而使得代码更加灵活和易于维护。

  2. 可读性: 使用装饰器可以清晰地表达出对类功能的增强,使得代码更加易于理解和阅读。

  3. 单一职责原则: 每个装饰器都可以负责一个特定的功能,从而遵循了单一职责原则,使得代码更加模块化和可复用。

总的来说,使用装饰器来增强类的功能是一种简单而灵活的方式,适用于需要动态地为类添加新功能的情况。

思路4:使用 Mixin

示例1

class Document: # 第三方库,不允许修改
    def __init__(self, content):
        self.content = content
class Word(Document): pass # 第三方库,不允许修改
class Pdf(Document): pass # 第三方库,不允许修改

class PrintableMixin:
    def print(self):
        print(self.content, 'Mixin')

class PrintableWord(PrintableMixin, Word): pass
print(PrintableWord.__dict__) # {'__module__': '__main__', '__doc__': None}
print(PrintableWord.mro()) # [<class '__main__.PrintableWord'>, <class '__main__.PrintableMixin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]


def printable(cls):
    def _print(self):
        print(self.content, '装饰器')
    cls.print = _print
    return cls

@printable
class PrintablePdf(Pdf): pass
print(PrintablePdf.__dict__) # {'__module__': '__main__', '__doc__': None, 'print': <function printable.<locals>._print at 0x000001E2709EB9A0>}
print(PrintablePdf.mro()) # [<class '__main__.PrintablePdf'>, <class '__main__.Pdf'>, <class '__main__.Document'>, <class 'object'>]

Mixin就是其它类混合进来,同时带来了类的属性和方法。

这里看来Mixin类和装饰器效果一样,也没有什么特别的,但Mixin是类,因此可以继承。

解释

Mixin 是一种将类的功能以组件的形式混合到其他类中的方式。在 Python 中,Mixin 通常是指一些只包含方法而不包含状态或数据的类。

在示例中,PrintableMixin 是一个具有 print 方法的 Mixin 类,它被混合到 PrintableWord 类中,以赋予 PrintableWord 类打印功能。而 PrintablePdf 则使用了装饰器的方式增强了打印功能。

Mixin 和装饰器虽然在这个例子中达到了相同的效果,但它们的应用场景和用法略有不同:

  1. Mixin 的优势

    • 可以将多个 Mixin 组合在一起,形成复杂的功能组合。
    • Mixin 可以继承自其他类,因此可以包含属性和方法,而不仅仅是单一功能。
  2. 装饰器的优势

    • 装饰器更加灵活,可以在运行时动态地为类添加新的方法或属性,而不需要修改类的定义。
    • 装饰器可以作用于任何类或函数,而 Mixin 则需要通过继承来实现功能的混合。

选择使用 Mixin 还是装饰器取决于具体的情况和需求。Mixin 适合于将一些相关的功能组合到一个类中,而装饰器则更适合于动态地为类添加新的功能。

示例2

class Document: # 第三方库,不允许修改
    def __init__(self, content):
        self.content = content
class Word(Document): pass # 第三方库,不允许修改
class Pdf(Document): pass # 第三方库,不允许修改

class PrintableMixin:
    def print(self):
        print(self.content, 'Mixin')

class PrintableWord(PrintableMixin, Word): pass
print(PrintableWord.__dict__) # {'__module__': '__main__', '__doc__': None}
print(PrintableWord.mro()) # [<class '__main__.PrintableWord'>, <class '__main__.PrintableMixin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]

pw = PrintableWord('test string')
pw.print() # test string Mixin

class SuperPrintableMixin(PrintableMixin):
    def print(self):
        print('~' * 20) # 打印增强
        super().print()
        print('~' * 20) # 打印增强

class SuperPrintablePdf(SuperPrintableMixin, Pdf): pass

print(SuperPrintablePdf.__dict__) # {'__module__': '__main__', '__doc__': None}
print(SuperPrintablePdf.mro()) # [<class '__main__.SuperPrintablePdf'>, <class '__main__.SuperPrintableMixin'>, <class '__main__.PrintableMixin'>, <class '__main__.Pdf'>, <class '__main__.Document'>, <class 'object'>]

spp = SuperPrintablePdf('super print pdf')
spp.print()
'''
~~~~~~~~~~~~~~~~~~~~
super print pdf Mixin
~~~~~~~~~~~~~~~~~~~~
'''

解释

在示例2中,我们定义了一个新的 Mixin 类 SuperPrintableMixin,它继承自 PrintableMixin,并且重写了 print 方法以增强打印功能。在重写的方法中,首先打印了一行波浪线,然后调用了 super().print() 来调用父类(即 PrintableMixin)的 print 方法,最后再打印一行波浪线。

然后,我们定义了一个新的类 SuperPrintablePdf,它混合了 SuperPrintableMixinPdf 类。由于 SuperPrintableMixin 中的 print 方法已经对打印功能进行了增强,因此 SuperPrintablePdf 类中的 print 方法会调用 SuperPrintableMixin 中的 print 方法,从而实现了对 Pdf 类的打印功能的增强。

这种方式的好处在于:

  1. 功能的复用: SuperPrintableMixin 中的增强打印功能可以被多个类复用,只需要将这个 Mixin 类混合到需要增强打印功能的类中即可。

  2. 灵活性: 可以通过继承 Mixin 类来对已有的功能进行扩展或增强,而不需要修改原始类的定义。

  3. 可维护性: 将功能模块化,使得代码更易于理解和维护。

总的来说,Mixin 类的方式可以很好地实现对类功能的增强和扩展,同时保持了代码的灵活性和可维护性。

Mixin 类

Mixin 本质上就是多继承实现的。

Mixin 体现的是一种组合的设计模式。

在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能又来自不同的类提供,这就需要很多的类组合在一起。

从设计模式的角度来说,多组合,少继承。

  • 组合优于继承

Mixin 类的使用原则:

  • Mixin 类中不应该显示的出现 __init__ 初始化方法
  • Mixin 类通常不能独立工作,因为它是准备混入别的类中的部分功能实现
  • Mixin 类的祖先类也应该是 Mixin 类

使用时,Mixin 类通常在继承列表的第一个位置,例如:class PrintableWord(PrintableMixin, Word): pass

Mixin类和装饰器,这两种方式都可以使用,看个人喜好。如果还需要继承就得使用Mixin类的方式。

解释

你的总结非常到位!

Mixin 本质上是通过多继承实现的,它体现了一种组合的设计模式,允许将多个功能组合到一个类中,从而实现代码的复用和模块化。

在面向对象设计中,一个类可能需要许多不同的功能,而这些功能往往来自不同的类。Mixin 提供了一种灵活的方式来将这些功能组合到一个类中,而不需要使用多层继承。

Mixin 类的使用原则非常重要,特别是避免在 Mixin 类中定义 __init__ 方法,以及将 Mixin 类放在继承列表的第一个位置,以确保正确的方法解析顺序。

与装饰器相比,Mixin 类更适合需要多个功能组合和继承的场景,而装饰器则更适合动态地为单个类添加功能。选择使用 Mixin 类还是装饰器取决于个人偏好和具体的需求,但需要注意根据实际情况选择合适的方式来设计和组织代码。

多继承是指一个类可以同时继承自多个父类。在 Python 中,多继承是通过在类定义时指定多个父类来实现的。

下面是一个简单的示例,展示了多继承的概念:

class Animal:
    def speak(self):
        print("Animal speaks")

class Pet:
    def play(self):
        print("Pet plays")

class Dog(Animal, Pet):
    def bark(self):
        print("Dog barks")

## 创建 Dog 类的实例
dog = Dog()

## 调用继承自父类 Animal 的方法
dog.speak()  # 输出: Animal speaks

## 调用继承自父类 Pet 的方法
dog.play()   # 输出: Pet plays

## 调用子类自己定义的方法
dog.bark()   # 输出: Dog barks

在这个例子中,Dog 类同时继承自 AnimalPet 两个父类。因此,Dog 类可以使用 Animal 类和 Pet 类中定义的方法,分别是 speak() 方法和 play() 方法。同时,Dog 类也可以定义自己的方法,比如 bark() 方法。

多继承使得类之间的关系更加灵活,但也可能引入一些复杂性和潜在的问题。例如,如果多个父类中有相同的方法名,可能会导致方法的调用不确定性,这种情况下需要通过方法解析顺序(MRO)来确定调用的顺序。因此,在使用多继承时需要谨慎考虑,避免出现混乱和不可预期的行为。

Mixin

Mixin 在 Python 中是一种常见的编程技巧,用于在类中组合和重用代码。Mixin 类通常不是作为独立的类来实例化的,而是被设计成用于扩展其他类的功能。

为什么使用 Mixin?

  1. 代码重用:Mixin 允许你将一组功能封装在一个类中,然后可以在多个不同的类中重用这些功能。

  2. 代码组织:通过将相关的功能组合成Mixin类,可以使代码更加模块化和可维护。

  3. 避免多重继承的问题:Python 不支持多重继承的所有组合方式,使用 Mixin 可以在不使用多重继承的情况下实现相似的功能。

如何定义 Mixin 类?

Mixin 类通常定义为一个只包含方法的类,它们不应该有 __init__ 方法。这是因为 Mixin 类的目的是为了被其他类所组合和重用,而不是被实例化。

class MixinClass:
    def mixin_method(self):
        # 实现功能的代码
        pass

如何使用 Mixin 类?

要在其他类中使用 Mixin,只需将 Mixin 类作为父类之一,通过多重继承来组合功能。

class BaseClass:
    # 基础类的功能

class MyClass(BaseClass, MixinClass):
    # 组合了基础类和 Mixin 类的功能

注意事项:

  1. 命名约定:Mixin 类通常以 “Mixin” 结尾,以明确表明其目的。

  2. 方法命名冲突:在组合多个 Mixin 类时,要注意方法命名的冲突。可以通过约定统一的方法前缀或者Python3中的super()方法解决。

  3. 不要滥用:Mixin 是一种强大的工具,但滥用会导致代码难以理解和维护。只在适当的情况下使用 Mixin。

  4. 避免 Diamond Inheritance(钻石继承)问题:当多个父类中存在相同的方法或属性时,可能会导致不明确的行为,这是需要特别注意的。

总的来说,Mixin 是一种强大的代码重用和组织工具,但需要谨慎使用,以避免引入混乱和不必要的复杂性。