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

Python零基础 木人张 3年前 (2020-04-12) 904次浏览 0个评论 扫描二维码
文章目录[隐藏]

面向对象编程过程中,我们可以从已有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()方法的对象。

小结

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。


木人张,版权所有丨如未注明 , 均为原创,禁止转载。
喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址