python笨办法学习-practice44

继承(Inheritance) VS 合成(Composition)

什么是继承

继承的用处,就是用来指明一个类的大部分或全部功能,都是从一个父类中获得的。当你写 classFoo(Bar) 时,代码就发生了继承效果,这句代码的意思是“创建一个叫 Foo 的类,并让他继承Bar”。当你这样写时,Python 语言会让 Bar 的实例所具有的功能都工作在 Foo 的实例上。这样可以让你把通用的功能放到 Bar 里边,然后再给 Foo 特别设定一些功能。

当你这么做的时候,父类和子类有三种交互方式

1. 子类上的动作完全等同于父类上的动作

2. 子类上的动作完全改写了父类上的动作

3. 子类上的动作部分变更了父类上的动作

我将通过代码向你一一展示。

隐式继承(Implicit Inheritance)

首先我将向你展示当你在父类里定义了一个函数,但没有在子类中定义的例子,这时候会发生隐式继承。

class Parent(object):  # 新建父类

    def implicit(self):        #    父类函数
        print("PARENT implicit()")

class Child(Parent):    #  新建子类——隐式继承
    pass    # 空代码,表明子类没有定义新函数,继承了父类

dad = Parent()
son = Child()

dad.implicit() # 父类输出
son.implicit() # 子类输出

#运行结果

PARENT implicit()

PARENT implicit()

class Child: 中的 pass 是在 Python 中创建空的代码区块的方法。这样就创建了一个叫 Child 的类,但没有在里边定义任何细节。在这里它将会从它的父类中继承所有的行为。运行起来就是这样:

PARENT implicit()

PARENT implicit()

就算我在第 16 行调用了 son.implicit() 而且就算 Child 中没有定义过 implicit 这个函数,这个函数依然可以工作,而且和在父类 Parent 中定义的行为一样。这就说明,如果你将函数放到基类中(也就是这里的 Parent ),那么所有的子类(也就是 Child 这样的类)将会自动获得这些函数功能如果你需要很多类的时候,这样可以让你避免重复写很多代码

显式覆写(Explicit Override)

有时候你需要让子类里的函数有一个不同的行为,这种情况下隐式继承是做不到的,而你需要覆写子类中的函数,从而实现它的新功能。你只要在子类 Child 中定义一个相同名称的函数就可以了,如下所示:

class Parent(object):  # 新建父类

    def overside(self):        #    父类函数
        print("父类——PARENT override()")

class Child(Parent):    #  新建子类

    def overside(self):  # 在子类中定义和父类相同的函数,为显示覆写
        print("子类——CHILD override()")

dad = Parent()
son = Child()

dad.overside() # 父类输出
son.overside() # 子类输出

#运行结果

父类——PARENT override()

子类——CHILD override()

本例子子类中新定义的函数在这里取代了父类里的函数

在运行前或运行后覆写

第三种继承的方法是一个覆写的特例,这种情况下,你想在父类中定义的内容运行之前或者之后再修改行为。首先你像上例一样覆写函数,不过接着你用 Python 的内置函数 super 来调用父类 Parent 里的版本。我们还是来看例子吧:

class Parent(object):  # 新建父类

    def altered(self):        #    父类函数
        print("父类——PARENT override()")

class Child(Parent):    #  新建子类

    def altered(self):  # 在子类中定义和父类相同的函数,为显示覆写
        print("子类——CHILD BEFORE PARENT altered)")
        super(Child,self).altered()  # 先运行父类,再改写
        print("super——CHILD AFTER PARENT altered)")


dad = Parent()
son = Child()

dad.altered() # 父类输出
son.altered() # 子类输出

#运行结果

父类——PARENT override()

子类——CHILD BEFORE PARENT altered)

父类——PARENT override()

super——CHILD AFTER PARENT altered)

为什么要用 super()

到这里也算是一切正常吧,不过接下来我们就要来应对一个叫多重继承(Multiple Inheritance)的麻烦东西。多重继承是指你定义的类继承了多个类,就像这样:

class SuperFun(Child, BadStuff):

 pass

这相当于说“创建一个叫 SuperFun 的类,让它同时继承 Child 和 BadStuff ”。这里一旦你在 SuperFun 的实例上调用任何隐式动作,Python 就必须回到类的层次结构中去检查Child 和 BadStuff ,而且必须要用固定的次序去检查。为实现这一点 Python 使用了一个叫 “方法解析顺序(Method Resolution Order,MRO)”的东西,还用了一个叫 C3 的算法。由于有这个复杂的 MRO 和这个很好的算法,Python 总不该把这些事情留给你去做吧,不然你不就跟着头大了?所以 Python 给你这个 super() 函数,用来在各种需要修改行为的场合为你处理,就像上面Child.altered 一样。有了 super() ,妈妈再也不用担心我吧继承关系弄糟,因为 Python 会给我找到正确的函数。

super() 和 __init__ 搭配使用

最常见的 super() 的用法是在基类的 __init__ 函数中使用。通常这也是唯一可以进行这种操作的地方,在这里你在子类里做了一些事情,然后完成对父类的初始化。这里是一个在 Child 中完成上述行为的例子:

class Child(Parent):    #  新建子类

    def altered(self):  # 在子类中定义和父类相同的函数,为显示覆写
        super(Child,self).__init__()  # 先运行父类,再改写
        print("super——CHILD AFTER PARENT altered)")

这和上面的 Child.altered 差别不大,只不过我在 __init__ 里边先设了个变量,然后才用Parent.__init__ 初始化了 Parent 。

class Other(object):
    def override(self):
        print("覆写-other override()")

    def implicit(self):
        print("隐式继承-other implicit()")

    def altered(self):
        print("other altered()")

class Child(object):

    def __init__(self):
        self.other = Other()

    def implicit(self):
        self.other.implicit()

    def override(self):
        print("Child override()")

    def altered(self):
        print("子类,before other altered()")
        self.other.altered()
        print("子类,after other altered()")

son = Child()

son.implicit()
son.override()
son.altered()

这里我没有使用 Parent 这个名称,因为这里不是父类子类的“A 是 B”的关系,而是一个“A 里有 B”的关系,这里 Child 里有一个 Other 用来完成它的功能。运行的时候,我们可以看到这样的输出:

隐式继承-other implicit()

Child override()

子类,before other altered()

other altered()

子类,after other altered()

继承和合成的应用场合

“继承 vs 合成”的问题说到底还是关于代码重用的问题。你不想到处都是重复的代码,这样既难看又没效率。继承可以让你在基类里隐含父类的功能,从而解决了这个问题。而合成则是利用模块和别的类中的函数调用实现了相同的目的。

如果两种方案都能解决重用的问题,那什么时候该用哪个呢?这个问题答案其实是非常主观的,不过我可以给你三个大体的指引方案:

1. 不惜一切代价地避免多重继承,它带来的麻烦比能解决的问题都多。如果你非要用,那你得准备好专研类的层次结构,以及花时间去找各种东西的来龙去脉吧。

2. 如果你有一些代码会在不同位置和场合应用到,那就用合成来把它们做成模块。

3. 只有在代码之间有清楚的关联,可以通过一个单独的共性联系起来的时候使用继承,或者你受现有代码或者别的不可抗拒因素所限非用不可的话,那也用吧。

然而,不要成为这些规则的奴隶。面向对象编程中要记住的一点是,程序员创建软件包,共享代码,这些都是一种社交习俗。由于这是一种社交习俗,有时可能因为你的工作同事的原因,你需要打破这些规则。这时候,你就需要去观察别人的工作方式,然后去适应这种场合。

PEP 0 – Index of Python Enhancement Proposals (PEPs) | peps.python.org

常见问题回答:

怎样增强自己解决新问题的技术?

提高解决问题能力的唯一方法就是自己去努力解决尽可能多的问题。很多时候人们碰到难题就会跑去找人给出答案。当你手头的事情非要完成不可的时候,这样做是没有问题的,不过如果你有时间自己解决的话,那就花时间去解决吧。停下手上的活,专注于你的问题死磕,试着用所有可能的方法去解决,不管最后解决与否都要试到山穷水尽为止。经过这样的过程,你找到的答案会让你更为满意,而你的解决问题的能力也提高了。

ps:本书的习题43,以及后面的习题目前来说都挺难的,本书的分享就先到这里了。学习路漫漫,仍需多多努力。

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值