学习目标:
- 了解继承与多态的概念,重点是要学会运用继承去处理问题
学习内容:
继承
在面对对象程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类,而被继承的class称为基类、父类或超类。
为了便于理解,我们可以举例子说明一下:
比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:
class Animal(object):
def run(self):
print('Animal is running...')
现在我们有了Animal这个类,基于这个动物类,我们可以再分出一个Dog类和Cat类。现在我们也想让这两个类拥有run方法,这时为了方便就可以去"借用"Animal类里的run方法:
class Dog(Animal):
pass
class Cat(Animal):
pass
现在按照继承定义的说法,对Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。
继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
'''
运行结果如下:
Animal is running...
Animal is running...
'''
继承的特点:
-
在调用父类的方法时,需要加上父类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数
-
Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到父类中逐个查找。(先在本类中查找调用的方法,找不到才去父类中找)。
-
继承中父类:用于被继承的类,称之为父类,也叫做基类,或者超类。子类:继承其他类的类,称之为子类,也叫做派生类。
-
实现代码复用,减少代码量;拥有父类的所有功能,只需要编写子类附加的功能即可。
继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
┌───────────────┐
│ object │
└───────────────┘
│
┌────────────┴────────────┐
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Animal │ │ Plant │
└─────────────┘ └─────────────┘
│ │
┌─────┴──────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dog │ │ Cat │ │ Tree │ │ Flower │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
这时候就可以提到继承里的一个特殊情况了,在python里可能有别与Java,在java里是不能直接继承多个父类的,而python是可以的只要在子类的小括号里用逗号隔开就行了。
多继承
Python2.2之前类是没有共同的祖先的,之后,引入object类,它是所有类的共同祖先类object。Python2中为了兼容,分为古典类(旧式类)和新式类。Python3中全部都是新式类。新式类都是继承自object的,新式类可以使用super。
语法结构
class ClassName(父类列表:A,B,C,...):
类的代码体
多继承的产生也就是为了去可以免除一些方法的反复编写,只要从写好的地方继承过了就可以了。由此就会带来一些缺点:
当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。Python语法是允许多继承,但Python代码是解释执行,只有执行到的时候,才发现错误。团队协作开发,如果引入多继承,那代码很有可能不可控。不管编程语言是否支持多继承,都应当避免多继承。Python的面向对象,我们看到的太灵活了,太开放了,所以要团队守规矩。
- 多继承很好的模拟了世界,因为事物很少是单一继承,但是舍弃简单,必然引入复杂性,带来了冲突。
- 多继承的实现会导致代码设计的复杂度增加,所以有些高级编程语言舍弃了类的多继承:C++支持多继承;Java舍弃了多继承。
- 实现多继承的语言,要解决二义性,深度优先或者广度优先。
多态
子类可以从父类继承相同的属性或者方法,比如相同的函数名,在子类中可以有不同的实现,即子类可以有自己的特殊性,这就叫做多态。
多态的话按照我的理解就是一个物体的多种形态,比如可口可乐,在原先的基础上现在又有了樱桃味的,无糖等等。就是让原本功能比较简单的类,去在原基础上可以添加其他功能,官方点说就是增加它的灵活性。
为了更加生动的理解多态,再次利用上面的例子:
现在就可以对子类增加一些方法,比如Dog类:
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
现在就可以看出,其实多态是基于继承来完成的。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running…,符合逻辑的做法是分别显示Dog is running…和Cat is running…,因此,对Dog和Cat类改进如下:
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
'''
再次运行,结果如下:
Dog is running...
Cat is running...
'''
当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就可以说多态是基于继承来完成的
。
现在就可以总结出实现多态有两个前提:
- 继承:多态必须发生在父类与子类之间
- 重写:子类重写父类方法
这时候就会有小白会问了那学了多态有什么用?
怎么说呢其实以上代码实际貌似没啥用,为什么一定要去定义通过同一个函数名?我定义run1、run2不可以吗?代码量也一样啊!
其实真正体验出多态好处的代码一般是这样写的
class Animal():
def run(self):
print("I am an Animal")
class Dog(Animal):
def run(self):
print("I am a dog")
class Cat(Animal):
def run(self):
print("I am a cat")
def func(obj):
obj.run()
if __name__ == "__main__":
dog=Dog()
cat=Cat()
func(dog)
func(cat)
仅仅需要一个函数,就可以把不同对象的run函数表现出来了。这就增加了程序的灵活性,以不变应万变,不管你类中的run()写得如何得天花乱坠,我都可以用一个统一的形式来调用。另外,它增加了程序的可扩展性,不管是我们或者调用者,如果想增加新的功能,都不需要修改原来的代码。
如,我想新增一个bird类,仅需增加以下代码,无需修改原来的。
class Bird(Animal):
def run(self):
print("I am a bird")
对于调用者,也仅需增加以下代码,无需修改原来的。
bird=Bird()
func(bird)
所以说多态有什么用?一是增加程序的灵活性,二是增加程序的可扩展性。
静态语言 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()方法的对象。
学习时间:
- 周一至周五晚上 7 点—晚上9点
- 周六上午 9 点-上午 11 点
- 周日下午 3 点-下午 6 点
学习产出:
- 技术笔记 2 遍
- CSDN 技术博客 3 篇