继承和多态

继承和多态——这是 Python 中非常重要的概念。如果你想学习,你必须更好地理解它。

继承

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

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

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

创建派生类的语法是 −

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

继承属性

现在查看下面的示例 −

继承属性

输出

继承属性输出

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

Object.Attribute 查找层次结构

  • 实例
  • 此类继承自的任何类

继承示例

让我们通过闭包看一下继承示例 −

继承示例

让我们创建几个类来参与示例 −

  • 动物 − 类模拟动物
  • 猫 − 动物的子类
  • 狗 − 动物的子类

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

子类的构造函数总是调用父类的构造函数来初始化父类中属性的值,然后开始为其属性赋值。

Python 构造函数

输出

Python 构造函数输出

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

如果子类尝试从另一个子类继承方法或数据,那么它将通过一个错误,正如我们看到的,当 Dog 类尝试从该 cat 类调用 swatstring() 方法时,它会抛出一个错误(如我们例子中的 AttributeError)。

多态性("多种形状")

多态性是 Python 中类定义的一个重要特性,当您在类或子类中拥有共同命名的方法时,就会用到它。这允许函数在不同时间使用不同类型的实体。因此,它提供了灵活性和松散耦合,因此可以随着时间的推移扩展和轻松维护代码。

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

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

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

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

Polymorphism

输出

多态性输出

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

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

Polymorphic

覆盖

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

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

示例

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!')

继承构造函数

从我们之前的继承示例中可以看出,__init__ 位于父类中,因为子类 dog 或 cat 中没有 __init__ 方法。Python 使用继承属性查找在 animal 类中查找 __init__。当我们创建子类时,它首先会在 dog 类中查找 __init__ 方法,然后找不到它,然后查看父类 Animal 并在那里找到并调用它。因此,随着类设计变得复杂,我们可能希望首先通过父类构造函数处理实例,然后通过子类构造函数对其进行初始化。

Constructor

输出

Constructor Output

在上面的例子中,所有动物都有名字,所有狗都有特定的品种。我们用 super 调用父类构造函数。所以 dog 有自己的 __init__,但首先发生的事情是我们调用 super。Super 是内置函数,旨在将类与其超类或其父类关联起来。

在这种情况下,我们说获取 dog 的超类并将 dog 实例传递给我们在此处称为构造函数 __init__ 的任何方法。换句话说,我们用 dog 对象调用父类 Animal __init__。您可能会问,为什么我们不直接对 dog 实例执行 Animal __init__ 操作,我们可以这样做,但如果 animal 类的名称将来会发生变化。如果我们想重新排列类层次结构,让 dog 从另一个类继承,该怎么办?在这种情况下使用 super 可以让我们保持模块化,易于更改和维护。

因此,在这个例子中,我们能够将通用 __init__ 功能与更具体的功能结合起来。这使我们有机会将通用功能与特定功能分开,从而消除代码重复,并以反映系统整体设计的方式将类相互关联。

结论

  • __init__ 与任何其他方法类似;它可以被继承

  • 如果一个类没有 __init__ 构造函数,Python 会检查其父类以查看是否可以找到一个。

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

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

  • 我们可能希望在父类以及我们自己的类中进行初始化。

多重继承和查找树

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

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

Python 多重继承语法

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

下面是一个例子 −

>>> 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

查看下面的多重继承图 −

多重继承

让我们通过一个例子来了解 Python 的"mro"特性。

输出

Python mro Feature Output

示例 3

让我们再举一个"钻石"的例子形状"多重继承。

菱形形状多重继承

上图将被视为模棱两可。从我们之前的示例理解"方法解析顺序",即 mro 将是 D→B→A→C→A,但事实并非如此。从 C 获取第二个 A 时,Python 将忽略前一个 A。因此在这种情况下 mro 将是 D→B→C→A。

让我们根据上图创建一个示例 −

方法解析顺序

输出

方法解析顺序输出

理解上述输出的简单规则是 - 如果同一个类出现在方法解析顺序中,则该类的较早出现将从方法解析顺序中删除。

总之 −

  • 任何类都可以从多个类继承

  • Python 在搜索继承类时通常使用"深度优先"顺序。

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

装饰器、静态方法和类方法

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

方法的工作方式与函数完全相同,除了方法的第一个参数是实例对象这一点。

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

  • 简单方法 − 在类之外定义。此函数可以通过提供实例参数来访问类属性:

def outside_func(():
  • Instance method

def func(self,)
  • 类方法 − 如果我们需要使用类属性

   @classmethod
def cfunc(cls,)
  • 静态方法 − 没有关于该类的任何信息

      @staticmethod
def sfoo()

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

类方法

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

语法

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。

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

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

静态方法

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

语法

class C(object):
   @staticmethod
   def fun(arg1, arg2, ...):
   ...
returns: a static method for function funself.
  • 静态方法既不能修改对象状态,也不能修改类状态。
  • 它们在可以访问的数据方面受到限制。

何时使用什么

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

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