继承
在面向对象的程序设计中,当我们定义一个class时候,可以从现有的class继承,新的class成为子类,被继承的class称为基类,父类或超类。
比如,编写一个名为Animal的class:
class Animal(object): def run(self): print('动物可以跑')
接着编写两个从Animal继承的类:Dog和Cat:
class Dog(Animal): pass class Cat(Animal): pass
那么,对于Dog、Cat来说,Animal就是父类,对于Animal来说,Dog、Cat就是子类。(其实Animal是object的子类……继承还可以一级一级的继承下来,形成继承树,就好比爸爸继承于爷爷,儿子继承于爸爸的关系。python中,任何类最终都可以追溯到根类object,面向对象中的上帝啊)
尝试运行:
>>> dog = Dog() >>> cat = Cat() >>> dog.run() 动物可以跑 >>> cat.run() 动物可以跑
上述结果告诉我们,子类什么事都没干的情况,就具有了父类的功能。这就是继承的好处。
再尝试做如下修改:
class Dog(Animal): def run(self): print('狗在跑') def eat(self): print('狗吃香香') class Cat(Animal): def run(self): print('猫在跑') def eat(self): print('猫吃鱼')
运行结果:
>>> dog = Dog() >>> cat = Cat() >>> dog.run() 狗在跑 >>> cat.run() 猫在跑 >>> dog.eat() 狗吃香香
上述例子,也展示了继承的第二个好处——可以对子类增加一些父类中没有的方法eat():继承可以把父类的所有功能直接拿过来,这样就不必从零做起,子类既可以新增特有的方法,同时也可以覆盖父类的方法。
同时,我们改写了run()方法。当子类和父类存在性相同的run()方法时,我们说子类的run()方法覆盖了父类的run()方法,运行子类的实例代码时,调用的是子类的run()方法,这就是继承的另外一个特点——多态。
多态
当我们定义一个class的时候,我们实际上就是定义了一个数据类型。这些自定义的数据类型和python内置的数据类型,本质上没有什么区别:
>>> a = list() >>> b = Animal() >>> c = Dog() >>> isinstance(a,list) True >>> isinstance(b,Animal) True >>> isinstance(c,Dog) True >>> isinstance(c,Animal) True
isinstance()函数可以判断变量是否是指定的类型。最后一条记录告诉我们,c不仅仅是Dog数据类型,还是Animal数据类型。仔细想想,Dog从Animal继承而来,c是Dog没错,当然同时也是Animal喽,换句自然界的说法:Dog本来就是Animal的一种。
反过来呢:
>>> isinstance(b,Dog)
False
可知:Dog可以看做是Animal,但是Animal却不能看成Dog。
多态有什么好处呢?实际上,任何依赖父类作为参数的函数或者方法都可以不加修改的正常运行下去。举例:
>>> def runAnimal(animal): ... animal.run() ... >>> runAnimal(Dog()) 狗在跑 >>> runAnimal(Cat()) 猫在跑
我们发现,任何Animal的子类,都可以在runAnimal()上运行,而不需要我们去修改方法内容。
总结一下多态:
对于一个变量,我们只需要知道他是Animal类型,并不需要知道他的子类,就可以调用run()方法,而具体调用的run()方法是作用在Animal、Dog还是Cat对象上,由运行时该类型的确切类型决定——即调用方只管调用,不需要关注细节,而当我们新增一种Animal的子类时,只需要确保run()方法编写正确即可,不用管是如何调用的。这就是‘开闭’原则:
对扩展开放:允许新增Animal的子类
对修改封闭:不需要修改依赖Animal类型的runAnimal()函数。
扩展:
对于静态语言例如java来说,如果规定了传入的参数是Animal类型,那么我们必须要保证传入的对象是Animal类型或者他的子类,否则会出错。
然而对于python这样的动态语言来说,则不一定需要传入Animal的类型,我们只需保证传入的对象具有一个run()函数即可:
>>> class T(object): ... def run(self): ... print('T run ^') ... >>> runAnimal(T()) T run ^
这就是动态语言的‘鸭子类型’,它并不要求严格的继承体系,一个对象只要‘看起来像鸭子,走起路来像鸭子’,那他就可以被看做是鸭子。