继承和多态

继承和多态 - 这是Python中一个非常重要的概念。我们必须更好地理解它。

继承

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

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

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

创建派生类的语法是 -

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

继承属性

现在看下面的代码例子 -


class Date(object):
    def get_date(self):
        return "2018-06-30"

class Time(Date):
    def get_time(self):
        return "09:09:09"

dt = Date()
print("Get date from Date class: ", dt.get_date())

tm = Time()
print("Get time from Time class: ", tm.get_time())
print("Get date from class by inheriting or calling Date class method: ", tm.get_date())

执行上面示例代码,得到以下结果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
Get date from Date class:  2018-06-30
Get time from Time class:  09:09:09
Get date from class by inheriting or calling Date class method:  2018-06-30

首先创建了一个名为Date的类,并将该对象作为参数传递,object是由Python提供的内置类。 之后创建了另一个名为time的类,并将Date类称为参数。 通过这个调用,可以访问Date类中的所有数据和属性。 正因为如此,创建的Time类对象tm中获取父类中get_date方法。

Object.Attribute查找层次结构

  • 实例
  • 当前类
  • 该类继承的任何父类

继承示例

让我们来看看一个继承的例子 -

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

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

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

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


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

    def eat(self, food):
        print('%s is eating %s , '%(self.name, food))

class Dog(Animal):

    def fetch(self, thing):
        print('%s goes after the %s !'%(self.name, thing))


class Cat(Animal):

    def swatstring(self):
        print('%s shreds the string! ' % (self.name))


d = Dog('Ranger')
c = Cat("Meow")

d.fetch("ball");
c.swatstring()
d.eat("Dog food")
c.eat("Cat food")
## 调用一个没有的定义的方法
#d.swatstring()

执行上面示例,得到以下代码 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
Ranger goes after the ball !
Meow shreds the string! 
Ranger is eating Dog food , 
Meow is eating Cat food ,

在上面的例子中,我们看到如何父类中的属性或方法,以便所有的子类或子类都会从父类继承那些属性。

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

多态性(“多种形状”)

多态性是Python中类定义的一个重要特性,当您在类或子类中使用通用命名方法时,可以使用它。 这允许功能在不同时间使用不同类型的实体。 所以,它提供了灵活性和松散耦合,以便代码可以随着时间的推移而扩展和轻松维护。

这允许函数使用任何这些多态类的对象,而不需要知道跨类的区别。

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

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

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


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

    def eat(self, food):
        print('%s is eating %s , '%(self.name, food))

class Dog(Animal):

    def fetch(self, thing):
        print("{0} wags {1}".format(self.name, thing))

    def show_affection(self):
        print("{0} wags tail ".format(self.name))

class Cat(Animal):

    def swatstring(self):
        print('%s shreds the string! ' % (self.name))

    def show_affection(self):
        print("{0} purrs ".format(self.name))

d = Dog('Ranger')
c = Cat("Meow")

d.show_affection()
c.show_affection()

执行上面示例代码,得到以下结果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
Ranger wags tail 
Meow purrs

所以,所有的动物都表现出喜爱(show_affection),都不太相同。 “show_affection”行为因此具有多态性,因为它根据动物的不同而采取不同的行为。 因此,抽象的“动物”概念实际上并不是“show_affection”,而是特定的动物(如狗和猫)具有动作“show_affection”的具体实现。

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

重载

在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__位于父类中,因为子类-DogCat没有__init__方法。 Python使用继承属性查找来查找动物类中的__init__。 当我们创建子类时,首先它会查找dog类中的__init__方法,如果它找不到它,则查找父类Animal,并在那里找到并在那里调用它。 因此,当类设计变得复杂时,可能希望初始化一个实例,首先通过父类构造函数然后通过子类构造函数处理它。参考以下示例代码 -


import random

class Animal(object):

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

class Dog(Animal):

    def __init__(self, name):
        super(Dog, self).__init__(name)
        self.breed = random.choice(['Doberman', 'German shepherd', 'Beagle'])

    def fetch(self, thing):
        print('%s goes after the %s !'%(self.name, thing))

d = Dog('黑皮')
print(d.name)
print(d.breed)

执行上面示例代码,得到以下结果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
黑皮
German shepherd

在上面的例子中,所有的动物都有一个名字,所有的狗都是一个特定的品种。我们用super来调用父类构造函数。所以Dog类有它自己的__init__方法,但首先调用的我们称之为超类。 Super是在函数中构建的,它是用来将一个类与它的超类或它的父类关联起来。

在这种情况下,我们说获取超类-Dog并将它实例传递给构造函数 - __init__。换句话说,我们用dog对象调用父类Animal.__init__方法。你可能会问,为什么不会只用Animal.__init__()Dog的实例,我们可以做到这一点,但如果Animal类的名字会在将来某个时候改变。如果想重新安排类层次结构,那么该Dog会从另一个类继承。在这种情况下使用super可以让保持模块化,易于更改和维护。

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

结论

  • __init__与任何其他方法一样; 它可以被继承
  • 如果一个类没有__init__构造函数,Python将检查其父类查找。
  • 只要找到有一个__init__构造函数,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”特性。


class A(object):

    def dothis(self):
        print('doing this in A')

class B(A):
    pass

class C(object):
    def dothis(self):
        print('doing this in C')

class D(B, C):
    pass

d_inst =  D()
d_inst.dothis()
print(D.mro())

执行上面示例代码,得到以下结果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
doing this in A
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>]

示例3

下面再来看另一个“菱形”多重继承的例子。

上图将被视为含糊不清。 从上之前的例子中了解“方法解析顺序”。 mro将是D→B→A→C→A,但事实并非如此。 在从C获得第二个A时,Python将忽略之前的A。因此在这种情况下mro将是D→B→C→A

我们来根据上面的图创建一个例子 -


class A(object):

    def dothis(self):
        print('doing this in A')

class B(A):
    pass

class C(A):
    def dothis(self):
        print('doing this in C')

class D(B, C):
    pass

d_inst =  D()
d_inst.dothis()
print(D.mro())

执行上面示例代码,得到以下结果 -

E:\worksp\pycharm\venv\Scripts\python.exe E:/worksp/pycharm/main.py
doing this in C
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

理解上述输出的简单规则是 - 如果在方法分辨率顺序中出现相同的类,则将从方法分辨率顺序中删除此类的早期外观。

总结如下 -

  • 任何类都可以从多个类继承
  • 搜索继承类时,Python通常使用“深度优先”顺序。
  • 但是,当两个类从同一个类继承时,Python将从该mro中消除该类的第一次出现。

装饰器,静态和类方法

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

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

我们可以根据行为方式来分类方法

  • 简单的方法 - 在类的外部定义。 该函数可以通过提供实例参数来访问类属性:
    def outside_func(():
    
  • 实例方法 -
    def func(self,)
    
  • 类方法 - 如果需要使用类属性
    @classmethod
    def cfunc(cls,)
    
  • 静态方法 - 没有关于该类的任何信息
    @staticmethod
    def sfoo()
    
    到目前为止,我们已经看到了实例方法,下面来了解其他两种方法。

1. 类方法

@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参数,它不能修改对象实例状态。

  • 它受到类的约束,而不是类的对象。
  • 类方法仍然可以修改适用于类的所有实例的类状态。

2. 静态方法

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

语法

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

什么时候使用

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

上一篇: 面向对象捷径 下一篇: 设计模式