从零开始学Python-Day40-继承和多态

面向对象编程过程中,我们可以从已有class继承,定义一个新的类class,这个类被称作子类Subclass,被继承者的类被称为基类、父类或者超类(Base class、Super class)。

例如我们编写了一个名为Animal的类:

>>> class Animal(object):

def run(self):

print('Animals are running...')

>>> animal = Animal()

>>> animal.run()

Animals are running...

当我们定义新的类Dog和Cat时,可以直接从Animal继承:

>>> class Dog(Animal):

pass


>>> class Cat(Animal):

pass


>>> dog = Dog()

>>> dog.run()

Animals are running...

>>> cat = Cat()

>>> cat.run()

Animals are running...

对于Dog和Cat,Animal就是它们的父类,它俩就是Animal的子类,都继承了Animal的run方法

当然可以对子类增加方法:

>>> class Dog(Animal):

def run(self):

print('Dog is running...')

def eat(self):

print('Dog is eating...')

相比直接从Animal继承的run,显然修改后的Dog类的run方法更符合逻辑。当子类和父类都存在相同方法run()时,子类的run覆盖了父类的run。

多态

当我们定义一个class时,实际上就是定义了一种数据类型,接下来我们看下面这组判断:

>>>a = list()

>>>b = Animal()

>>>c = Dog()

>>> isinstance(a, list)

True

>>> isinstance(b, Animal)

True

>>> isinstance(c, Dog)

True

>>> isinstance(c, Animal)

True

isinstance用来判断一个变量是否为某个类型,我们看到a,b,c都符合对应的类型;但是c除了对应Dog类型,同时也是Animal类型。

如上,在继承关系中,如果一个实例的数据类型是一个子类,它的数据类型也可以看做父类,但反过来是不行的:

>>> b = Animal()

>>> isinstance(b, Dog)

False

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

>>> def run_twice(animal):

animal.run()

animal.run()


>>> run_twice(Animal())

Animals are running...

Animals are running...

>>> run_twice(Dog())

Dog is running...

Dog is running...

我们再定义一个Animal的子类Mouse:

>>> class Mouse(Animal):

def run(self):

print('Mouse is so small...')


>>> run_twice(Mouse())

Mouse is so small...

Mouse is so small...

可以看到对于新增的子类Mouse,不必修改run_twice,依赖父类Animal作为参数的函数或方法都可以不加修正的正常使用,原因就是多态!

多态的好处就在于此,我们要传入子类时,只要接收父类就可以,例如Dog、Cat都是Animal类型。

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

1、对扩展开放:允许新增Animal子类;

2、对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:

                ┌───────────────┐

                │    object    │

                └───────────────┘

                        │

          ┌────────────┴────────────┐

          │                        │

          ▼                        ▼

    ┌─────────────┐          ┌─────────────┐

    │  Animal    │          │    Plant    │

    └─────────────┘          └─────────────┘

          │                        │

    ┌─────┴──────┐            ┌─────┴──────┐

    │            │            │            │

    ▼            ▼            ▼            ▼

┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐

│  Dog  │  │  Cat  │  │  Tree  │  │ Flower  │

└─────────┘  └─────────┘  └─────────┘  └─────────┘

静态语言 vs 动态语言

对于静态语言(如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

class Timer(object):

    def run(self):

        print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。

许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

小结

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Sub...
    chen_000阅读 1,657评论 1 1
  • 程序在运行的过程中,根据传递的参数的不同,执行不同的函数或者操作不同的代码,这种在运行过程中才确定调用的方式成为运...
    云Shen不知处阅读 2,976评论 0 0
  • 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Sub...
    Cookie_hunter阅读 1,651评论 0 0
  • python学习笔记,特做记录,分享给大家,希望对大家有所帮助。 继承和多态 在OOP程序设计中,当我们定义一个c...
    Swift社区阅读 2,498评论 0 1
  • 今天也来写一个2017年总结。今天连赶了2场年饭后,终于发烧了。都不记得发烧是多少年的事情了,这回终于体会到,...
    御巢安王姗淇阅读 1,384评论 2 0