写在前面的话
终于,又到了周五。当小伙伴们都不再加班欢欢喜喜过周末的时候,我刚刚写完这一周的游戏作业,从面对晚归的紧皱眉头到现在的从容淡定,好像只有那么几周的时间。突然发现:改变——原来这么简单。很多时候我们在想要为自己的青春拼搏一次的时候都输给了偶尔的抵触和轻松愉悦的生活。我们不过和走到最后的人差了两个字——坚持!所以尽管进入类和对象这一部分,大家都会有畏难心理,但是坚持一下下,你就会是那个走到最后的人!
回顾
上一篇中我们初步的认识了类和对象,了结了它们的一些语法,就相当于得到了一个对象的骨骼。今天再来补充一些和他们相关的内容,给我们的小骷髅添上肌肉和皮肤,它就是一个活生生的对象了,从此你也是一个有对象的人了!哈~
首先我们从面向对象的特性这个角度先回顾一下上一篇的内容,之前我们已经知道了“车”是一个类,车的“品牌”和“价格”是它的属性,可以在路上跑是他的行为;因此我们定义了一个类car,并在这个car类中定义了price和type两个属性,还有一个DriveDistance方法。我们说,这个car类就像是一个收纳包,把和“车”有关的零散属性、方法都装进了包里,这就是面向对象的封装性。
面向对象的三大特性
上一篇我们讲的主要内容都符合面向对象的封装特性。那么问题来了?面向对象难道只有封装性么?当然不是,作为一个这么难理解的东西,要是只有封装性都对不起我们死了这么多脑细胞!所以,晴天霹雳来了,面向对象有三大特性,他们分别是:封装、继承和多态。
好消息和好消息和好消息,好消息一:封装我们已讲完,所以三座大山我们已经移走了一座,好消息二:由于python的特殊性,多态的应用并不广泛,所以我们其实还有一座半就胜利了,好消息三:前面那两条好消息都是真的。闲话少叙,今天咱们就聊聊继承,移走一座是一座!
正题——面向对象的继承性
一、继承
大学同学聚会,同桌吃饭,我们都是人,都有吃饭、喝饮料这些行为,但是毕业之后大家都做了不同的工作,有的当了会计、有的做了程序员,现在我们得到了描述这些同学这个需求,我们一看非常开心,我们可以实现呀,然后写下了下面左图的代码:
我们看上面左侧的代码,这么写确实实现了我们的需求,但是,写了那么多行,真正不一样的只有黄色框框里面的内容,好在大学同学的职业都差不多,这要是高中聚会可就热闹了。这个时候,我们就想,有没有可能我们不重复写之前的代码,也实现同样的功能呢?当然啦!→_→右侧这段代码。看着就简洁了不少,这就是类的继承。现在你看着好像有点儿迷糊,没关系,这里只需要知道有一种简单的方法可以实现,这种写法就叫做继承。具体我们后面还要详细讲。
我们来详细看看上面这张图,解释一下什么叫做继承,首先在最上面的黄框框里,我们定义了一个类叫做classmate,这个里面放了吃、喝两个方法,下面我们又定义了两个类,pythoner和accounting类,里面各写了一个occupation方法,打印出了人物的职业。我们看到,classmate类和我们之前见到的类并没什么不同,可是pythoner和accounting类定义的时候,我们看红框框里写了classmate类的类名,我们说,这样就实现了继承。pythoner和accounting类集成了classmate的所有属性和方法。
说完了继承类的定义,我们再来看看实例化和调用,我们看上面右侧那张小图,我们分别实例化了两个对象,eva和sweet,注意看红框框里我们实例化的是pythoner和accounting这两个派生类,但是我们却可以调用classmate的eat和drink方法,而且我们在occupation中也可以使用父类的name属性。magic!代码在下面~
1 classclassmate():2 def __init__(self,name):3 self.name =name4 defeat(self):5 print '%s is eating.'%self.name6 defdrink(self):7 print '%s is drinking'%self.name8
9
10 classpythoner(classmate):11 defoccupation(self):12 print '%s is a pythoner.'%self.name13
14 classaccounting(classmate):15 defoccupation(self):16 print '%s is a accounting.'%self.name17
18 eva = pythoner('eva')19 eva.occupation()20 eva.eat()21 eva.drink()22
23 sweet = accounting('sweet')24 sweet.occupation()25 sweet.eat()26 sweet.drink()
class Inheritanc
二、多继承
现在我们基本可以使用类的继承描述同坐一桌的同学们了,但是我们现在又有了一个新需求,就是把这一桌的男生和女生分开,男生喝酒,女生喝饮料,这个需求怎么用类来区别呢?先上图~
我们看上面左侧这张图,由于需求的增加,要求把同桌的男生和女生分开,我又新写了两个female和male类重新定义drink方法,并且又定义了新的类fe_pythoner和ma_pythoner,所以这两个类什么也不做,只是分别继承female、classmate和male、classmate类。在实例化对象的时候我们使用fe_pythoner和ma_pythoner,我们这样猜想,这个时候对象eva和sweet是不是应该分别去调用female和male中的drink方法呢?执行下,看看下面的结果。什么鬼?竟然还是输出了基类的drink方法。再看看右边,没错,我只是在定义基类的时候让基类继承了object,它就可以按照我们想要的方法输出了。
三、经典类的深度优先和新式类的广度优先
那么原理是什么呢?这个时候我就要盗一张图来解释这个问题了:
我们看上面的图,先放两句概念上来吓唬吓唬你:
当类是经典类时,多继承情况下,会按照深度优先方式查找
当类是新式类时,多继承情况下,会按照广度优先方式查找
那么什么是经典类和新式类呢?简而言之,继承自object的类就叫做新式类,object类是python提供的,现在我们还不需要管它从哪里来,因为让类中的很多操作变得更合理了,我们以后记着就这么写就对了。注:下面小伙伴提到,python3.X版本中的类继承默认就是广度优先。
下面来说广度优先和深度优先,首先,B和C两个类都必须继承自D,A类又继承自B、C,就是针对这种情况,没有为什么。。。背下来!我们对应起来看,这里的基类D就是上例中的classmate,BC就是pythoner和female,A则对应fe_pythoner类。
经典类中:当我们这样写:fe_pythoner(pythoner,female),对象调用方法的时候,会先在fe_pythoner里面找,然后依次去找pythoner、classmate、最后再找female。如果找到了,就会执行,并且不再继续找下去了。所以我们刚刚在左侧举出得栗子中它先找到了classmate中的drink方法,才打印出了同样的内容。这就是深度优先。
新式类中:当我们这样写:fe_pythoner(pythoner,female),对象调用方法的时候,会先在fe_pythoner里面找,然后依次去找pythoner、female、最后再找classmate。如果找到了,就会执行,并且不再继续找下去了。所以我们刚刚在右侧举得栗子中它先找到了female、或male中的drink方法,就打印了不同的内容。这就是广度优先。
1 classclassmate(object):2 def __init__(self,name):3 self.name =name4
5 defeat(self):6 print '%s is eating.'%self.name7
8 defdrink(self):9 print '%s is drinking'%self.name10
11 classfemale(classmate):12
13 defdrink(self):14 print '%s drink orange juice'%self.name15
16 classmale(classmate):17
18 defdrink(self):19 print '%s drink alcohol'%self.name20
21 classpythoner(classmate):22
23 defoccupation(self):24 print '%s is a pythoner.'%self.name25
26
27
28 classfe_pythoner(pythoner,female):29 pass
30 classmale_pythoner(pythoner,male):31 pass
32
33 eva = fe_pythoner('eva')34 eva.drink()35
36 sweet = male_pythoner('sweet')37 sweet.drink()
example Code
如果上面那些你通通没搞清楚,也没关系,在继承的时候可以直接把female类写在前面 fe_pythoner(female,pythoner),这么一来不管怎么样,都是先找female了。
好了,到现在为止我们现在已经移走了面向对象的第二座大山,离胜利又进了一步,感谢努力的自己,又多坚持了一下~~~