面向对象编程过程中,我们可以从已有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()方法的对象。
小结
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。