继承和多态



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

继承

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

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

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

创建派生类的语法为:

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

继承属性

现在看看下面的例子:

Inheriting Attributes

输出

Inheriting Attributes Output

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

对象.属性查找层次结构

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

继承示例

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

Inheritance Example

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

  • Animal - 模拟动物的类
  • Cat - Animal的子类
  • Dog - Animal的子类

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

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

Python Constructor

输出

Python Constructor Output

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

如果子类试图从另一个子类继承方法或数据,则会抛出错误,就像我们在Dog类尝试从Cat类调用swatstring()方法时看到的那样,它会抛出错误(在我们的例子中是AttributeError)。

多态(“多种形态”)

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

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

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

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

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

Polymorphism

输出

Polymorphism Output

因此,所有动物都表现出感情(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

查看下面的多重继承图:

Multiple Inheritance

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

输出

Python mro Feature Output

示例3

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

Diamond Shape Multiple Inheritance

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

让我们根据上面的图表创建一个例子:

Method Resolution Order

输出

Method Resolution Order Output

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

总之:

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

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

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

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

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

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

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

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

def outside_func(():
  • 实例方法 -

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

何时使用什么

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

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

广告