Object Oriented Python 简明教程

Inheritance and Polymorphism

继承和多态性——这是 Python 中一个非常重要的概念。如果你想学习,你必须更深入地了解它。

Inheritance

面向对象编程的主要优势之一是重用。继承就是实现这一目标的机制之一。继承允许程序员首先创建一个通用类或基类,然后稍后将其扩展为更专门的类。它允许程序员编写更好的代码。

使用继承,你可以使用或继承基类中所有可用的数据字段和方法。稍后,你可以添加自己方法和数据字段,因此继承提供了一种组织代码的方法,而不是从头开始重写它。

在面向对象术语中,当类 X 扩展类 Y 时,Y 被称为超类/父类/基类,X 被称为子类/子类/派生类。这里需要注意的一点是只有非私有的数据字段和方法可供子类访问。私有数据字段和方法仅可在类内访问。

创建派生类的语法为 -

class BaseClass:
   Body of base class
class DerivedClass(BaseClass):
   Body of derived class

Inheriting Attributes

现在,看下面的示例 -

inheriting attributes

Output

inheriting attributes output

我们首先创建一个名为 Date 的类,并传递对象作为参数,这里的对象是由 Python 提供的内置类。稍后,我们创建另一个名为 time 的类,并调用 Date 类作为参数。通过此调用,我们可以访问时间类中日期类的所有数据和属性。正因为如此,当我们尝试从我们之前创建的时间类对象 tm 中获取 get_date 方法时,这是可能的。

Object.Attribute Lookup Hierarchy

  1. The instance

  2. The class

  3. 该类继承的所有类

Inheritance Examples

让我们仔细看看继承示例——

inheritance example

让我们创建两个类来参与示例 -

  1. 动物——模拟动物的类

  2. 猫——动物的子类

  3. 狗 - 动物子类

在 Python 中,用于创建对象(实例)并为属性分配值的是类的构造函数。

子类构造函数总是调用父类构造函数以初始化其属性值,然后开始为其属性分配值。

python constructor

Output

python constructor output

在上面的示例中,我们看到在父类中放置的命令属性或方法,以便所有子类或子类都将从父类继承该属性。

如果子类尝试从另一个子类继承方法或数据,那么它将引发错误,就像我们看到 Dog 类试图从该 cat 类中调用 swatstring() 方法时,它会引发错误(在本例中类似 AttributeError)。

Polymorphism (“MANY SHAPES”)

多态性是 Python 中类定义的一项重要功能,在您跨类或子类拥有同名方法时使用。这允许函数在不同时间使用不同类型的实体。所以,它提供了灵活性并松散耦合,以便随着时间的推移可以扩展和容易维护代码。

这允许函数使用任何这些多态类的对象,而无需了解跨类的区别。

多态性可以通过继承来实现,其中子类使用基类方法或重写它们。

让我们用我们之前关于继承的示例来理解多态性的概念,并在两个子类中添加一个称为 show_affection 的通用方法 −

从示例中我们可以看到,它指的是一种设计,其中不同类型对象可以以相同的方式被对待,或者更具体地说,两个或多个具有同名方法或通用接口的类,因为相同的方法(在本例中为 show_affection)被使用任一类型的对象调用。

polymorphism

Output

polymorphism output

因此,所有动物都会表现出感情(show_affection),但它们表现方式不同。“show_affection” 行为因此是多态的,因为它的行为因动物而异。因此,抽象的“动物”概念实际上不会“show_affection”,但特定的动物(例如狗和猫)具有动作“show_affection” 的具体实现。

Python 本身具有多态的类。例如,len() 函数可以与多个对象一起使用,并且所有函数都根据输入参数返回正确的输出。

polymorphic

Overriding

在 Python 中,当一个子类包含一个覆盖超类方法的方法时,还可以通过调用来调用超类方法

Super(Subclass, self).method 而不是 self.method。

Example

class Thought(object):
   def __init__(self):
      pass
   def message(self):
      print("Thought, always come and go")

class Advice(Thought):
   def __init__(self):
      super(Advice, self).__init__()
   def message(self):
      print('Warning: Risk is always involved when you are dealing with market!')

Inheriting the Constructor

如果从我们之前的继承示例中可以看到, init 位于父类中的向上,因为子类 dog 或 cat 没有 init 方法。Python 使用继承属性查找来在 animal 类中查找 init 。当我们创建子类时,它将首先在 dog 类中查找 init 方法,然后在未找到后查看父类 Animal 并找到它们并进行调用。因此,随着我们类设计的变得复杂,我们可能希望首先通过父类构造函数,然后通过子类构造函数来初始化一个实例。

constructor

Output

constructor output

在上例中——所有动物都有一个名称,所有狗都有一个特定的品种。我们使用 super 来调用父类构造函数。因此,dog 有它自己的 init ,但首先发生的是我们调用 super。Super 是内置函数,它旨在将一个类与其超类或其父类相关联。

在这种情况下,我们所说的是获取 dog 的超类并将 dog 实例传递给我们在构造函数中所说的任何方法 init 。因此,换句话说,我们使用 dog 对象调用父类 Animal init 。你可能想知道,为什么我们不直接使用 dog 实例来说 Animal init ,我们也可以这样做,但是如果 animal 类名称在将来某个时间发生更改怎么办。如果我们想要重新排列类层次结构,那么 dog 会从另一个类继承。在这种情况下使用 super 会让事情保持模块化,并且易于更改和维护。

因此,在这个示例中,我们能够将通用 init 功能与更具体的功能相结合。这给了我们机会将通用功能与特定功能分开,这可以消除代码重复,并且以反映系统整体设计的方式将类相互关联。

Conclusion

  1. init 就像其他任何方法一样;它可以被继承

  2. 如果一个类没有 init 构造函数,Python 将检查其父类以查看是否可以找到一个。

  3. 一旦找到,Python 就会调用它并停止查找

  4. 我们可以使用 super() 函数来调用父类中的方法。

  5. 我们可能希望在父类和我们自己的类中进行初始化。

Multiple Inheritance and the Lookup Tree

顾名思义,多重继承是 Python 中的一个类从多个类继承的情况。

例如,一个孩子从父母(母亲和父亲)双方继承性格特征。

Python Multiple Inheritance Syntax

要让一个类从多个父类继承,我们在定义它时将这些类的名称写在派生类的圆括号中。我们用逗号分隔这些名称。

下面是一个示例:

>>> class Mother:
   pass

>>> class Father:
   pass

>>> class Child(Mother, Father):
   pass

>>> issubclass(Child, Mother) and issubclass(Child, Father)
True

多重继承是指从两个或两个以上的类中继承的能力。当孩子从父母继承,而父母从祖父母类继承时,复杂性就会出现。Python 会爬一个继承树,寻找要从某个对象读取的属性。它将在实例中、类内、父类中和最后从祖父母类中进行检查。现在出现的一个问题是,以什么顺序搜索这些类——广度优先还是深度优先。默认情况下,Python 使用深度优先。

这就是为什么在下面的图表中,Python 首先在类 A 中搜索 dothis() 方法。因此,以下示例中的方法解析顺序为:

Mro- D→B→A→C

查看下面的多重继承图表:

multiple inheritance

让我们通过一个示例来了解 Python 的“mro”特性的。

Output

python mro feature output

Example 3

让我们再举一个“菱形”多重继承的示例。

diamond shape multiple inheritance

上面的图表将被认为是模棱两可的。从我们之前的示例中理解“方法解析顺序” .i.e. mro 将为 D→B→A→C→A,但事实并非如此。从 C 中获得第二个 A 时,Python 将忽略前面的 A。因此,在这种情况下,mro 将为 D→B→C→A。

让我们基于上面的图表创建一个示例:

method resolution order

Output

method resolution order output

理解以上输出的简单规则是——如果同一个类出现在方法解析顺序中,则这个类的早期出现将从方法解析顺序中移除。

总结:

  1. 任何类都可以从多个类继承

  2. Python 在搜索继承类时通常使用“深度优先”顺序。

  3. 但是当两个类从同一个类继承时,Python 会从 mro 中消除该类的第一次出现。

Decorators, Static and Class Methods

函数(或方法)通过 def 语句创建。

尽管方法在以下一点上的工作方式与函数完全相同,但方法的第一个参数是实例对象。

我们可以根据其行为方式对方法进行分类,例如:

  1. Simple method - 在类外部定义。此函数可以通过提供实例参数来访问类属性:

def outside_func(():
  1. Instance method

def func(self,)
  1. Class method - 如果我们需要使用类属性

   @classmethod
def cfunc(cls,)
  1. Static method - 没有任何关于类的信息

      @staticmethod
def sfoo()

到目前为止,我们已经看到了实例方法,现在是时候深入了解其他两种方法了,

Class Method

@classmethod 装饰器是一个内置函数装饰器,它将作为第一个参数传递调用它的类,或调用它的实例的类。该评估的结果会隐藏您的函数定义。

syntax

class C(object):
   @classmethod
   def fun(cls, arg1, arg2, ...):
      ....
fun: function that needs to be converted into a class method
returns: a class method for function

他们有权访问此 cls 参数,它不能修改对象实例状态。这需要访问 self。

  1. 它绑定到类,而不是类的对象。

  2. 类方法仍然可以修改适用于类的所有实例的类状态。

Static Method

静态方法不接受 self 或 cls(class) 参数,但它可以自由地接受任意数量的其他参数。

syntax

class C(object):
   @staticmethod
   def fun(arg1, arg2, ...):
   ...
returns: a static method for function funself.
  1. 静态方法既不能修改对象状态,也不能修改类状态。

  2. 他们可以访问的数据受到限制。

When to use what

  1. 我们通常使用类方法创建工厂方法。工厂方法为不同的用例返回类对象(类似于构造函数)。

  2. 我们通常使用静态方法创建实用函数。