笨方法学python 习题44

习题44
python:3.9
继承(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()
    

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

PARENT implicit()
PARENT implicit()

就算我在第 16 行调用了 son.implicit()而且就算 Child中没有定义过 implicit这个函数,这个函数依然可以工作,而且和在父类 Parent中定义的行为一样。这就说明,如果你将函数放到基类中(也就是这里的 Parent),那么所有的子类(也就是 Child这样的类)将会自动获得这些函数功能。如果你需要很多类的时候,这样可以让你避免重复写很多代码。
显式覆写(Explicit Override)
有时候你需要让子类里的函数有一个不同的行为,这种情况下隐式继承是做不到的,而你需要覆写子类中的函数,从而实现它的新功能。你只要在子类 Child中定义一个相同名称的函数就可以了,如下所示:

class Parent(object):

    def override(self):
        print("PARENT override()")
        
class Child(Parent):

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

dad = Parent()
son = Child()

dad.override()
son.override()

这里我在两个类中都定义了一个叫override的函数,我们看看运行时会出现什么情况

PARENT override()
CHILD override()

如你所见,运行到第 14 行时,这里执行的是 Parent.override,因为 dad这个变量是定义在 Parent里的。不过到了第 15 行打印出来的却是 Child.override里的信息,因为 son是 Child的一个实例,而子类中新定义的函数在这里取代了父类里的函数。
在运行前或运行后覆
写第三种继承的方法是一个覆写的特例,这种情况下,你想在父类中定义的内容运行之前或者之后再修改行为。首先你像上例一样覆写函数,不过接着你用 Python 的内置函数 super来调用父类 Parent里的版本。我们还是来看例子吧:

class Parent(object):

    def altered(self):
        print("PARENT alterred()")
        
class Child(Parent):

    def altered(self):
        print("CHILD,BEFORE PARENT altered()")
        super(Child,self).altered()
        print("CHILD,AFTER PARENT altered()")
        
dad = Parent()
son = Child()

dad.altered()
son.altered()      

重要的是 9 到 11 行,当调用 son.altered()时:
1.由于我覆写了 Parent.altered,实际运行的是 Child.altered,所以第 9 行执行结果是预料之中的。
2.这里我想在前面和后面加一个动作,所以,第 9 行之后,我要用 super来获取 Parent.altered这个版本。
3.第 10 行我调用了 super(Child, self).altered(),这和你过去用过的 getattr很相似,不过它还知道你的继承关系,并且会访问到 Parent类。这句你可以读作“调用 super并且加上 Child和 self这两个参数,在此返回的基础上然后调用 altered ”。
4.到这里Parent.altered就会被运行,而且打印出了父类里的信息。
5.最后从 Parent.altered返回到 Child.altered,函数接着打印出来后面的信息
运行结果如下

PARENT alterred()
CHILD,BEFORE PARENT altered()
PARENT alterred()
CHILD,AFTER PARENT altered()

一起使用三种方式为了演示上面讲的内容,我来写一个最终版本,我们在一个文件中演示三种交互模式:

class Parent(object):

    def override(self):
        print("PARENT override()")
        
    def implicit(self):
        print("PARENT implicit()")

    def altered(self):
        print("PARENT altered()")
        
        
class Child(Parent):

    def override(self):
        print("CHILD override()")
        
    def altered(self):    
        print("CHILD,BEFORE PARENT altered()")
        super(Child,self).altered()
        print("CHILD,AFTER PARENT altered()")
        
dad = Parent()
son = Child()

dad.implicit()
son.implicit()

dad.override()
son.override()

dad.altered()
son.altered()        

回到代码中,在每一行的上方写一个注解,写出它的功能,并且标出它是不是一个覆写动作。然后运行代码,看看输出的是不是你预期的内容:

PARENT implicit()
PARENT implicit()
PARENT override()
CHILD override()
PARENT altered()
CHILD,BEFORE PARENT altered()
PARENT altered()
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 __init__(self,stuff):
        self.stuff = stuff
        super(Child,self).__init__()

这和上面的 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("CHILD,BEFORE OTHER altered()")
        self.other.altered()
        print("CHILD,AFTER OTHER altered()")
        
son = Child()

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

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

OTHER implicit()
CHILD override()
CHILD,BEFORE OTHER altered()
OTHER altered()
CHILD,AFTER OTHER altered()

你可以看出, Child和 Other里的大部分内容是一样的,唯一不同的是我必须定义一个 Child.implicit函数来完成它的功能。然后我可以问自己,这个 Other是写成一个类呢,还是直接做一个叫other.py的模块比较好?
继承和合成的应用场合
“继承 vs 合成”的问题说到底还是关于代码重用的问题。你不想到处都是重复的代码,这样既难看又没效率。继承可以让你在基类里隐含父类的功能,从而解决了这个问题。而合成则是利用模块和别的类中的函数调用实现了相同的目的。

如果两种方案都能解决重用的问题,那什么时候该用哪个呢?这个问题答案其实是非常主观的,不过我可以给你三个大体的指引方案:
1.不惜一切代价地避免多重继承,它带来的麻烦比能解决的问题都多。如果你非要用,那你得准备好专研类的层次结构,以及花时间去找各种东西的来龙去脉吧。
2.如果你有一些代码会在不同位置和场合应用到,那就用合成来把它们做成模块。
3.只有在代码之间有清楚的关联,可以通过一个单独的共性联系起来的时候使用继承,或者你受现有代码或者别的不可抗拒因素所限非用不可的话,那也用吧。
然而,不要成为这些规则的奴隶。面向对象编程中要记住的一点是,程序员创建软件包,共享代码,这些都是一种社交习俗。由于这是一种社交习俗,有时可能因为你的工作同事的原因,你需要打破这些规则。这时候,你就需要去观察别人的工作方式,然后去适应这种场合。
加分习题
本节只有一个加分习题,不过这个加分习题很大。去读一读 http://www.python.org/dev/peps/pep-0008/并在代码中应用它。
你会发现其中有一些东西和本书中的不一样,不过你现在应该能懂得他们的推荐,并在自己的代码中应用这些规范。本书剩下的部分可能有一些没有完全遵循这些规范,不过这是因为有时候遵循规范反而让代码更难懂。我建议你也照做,因为对代码的理解比对风格规范的记忆更为重要。
常见问题回答
怎样增强自己解决新问题的技术?
提高解决问题能力的唯一方法就是自己去努力解决尽可能多的问题。很多时候人们碰到难题就会跑去找人给出答案。当你手头的事情非要完成不可的时候,这样做是没有问题的,不过如果你有时间自己解决的话,那就花时间去解决吧。停下手上的活,专注于你的问题死磕,试着用所有可能的方法去解决,不管最后解决与否都要试到山穷水尽为止。经过这样的过程,你找到的答案会让你更为满意,而你的解决问题的能力也提高了。对象是不是就是类的拷贝?有的语言里是这样的,例如 Javascript。这样的语言叫做prototype 语言,这种语言里的类和对象除了用法以外没多少不同。不过在 Python 里类其实像是用来创建对象的模板,就跟制作硬币用到的模具一样。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值