课时38:类与对象:继承

发布时间:2019-03-03 10:22:00编辑:auto阅读(2119)

    目录:
      一、继承

      二、调用未绑定的父类方法

      三、使用super函数

      四、多重继承

      五、课时38课后习题及答案

     

    现在需要扩展游戏,对鱼类进行细分,有金鱼(Goldfish)、三文鱼(Salmon)、鲤鱼(Carp),还有鲨鱼(Shark)。那么我们来思考一个问题:能不能不要每次都从头到尾去重新定义一个新的鱼类呢?因为我们知道大多数鱼的属性和方法是相似的,如果有一种机制可以让这些相似的东西得以自动传递,那就方便快捷多了。这种机制就是今天要讲的:继承。

    ***********

    一、继承

    ***********

     语法很简单:

    class 类名(被继承的类):
            ...

    被继承的类称为基类、父类或超类;继承者称为子类,一个子类可以继承它的父类的任何属性和方法。举个例子:

    >>> class Parent:
        def hello(self):
            print("正在调用父类的方法...")
    
            
    >>> class Child(Parent):
        pass
    
    >>> p = Parent()
    >>> p.hello()
    正在调用父类的方法...
    >>> c = Child()
    >>> c.hello()
    正在调用父类的方法...

    需要注意的是:如果子类中定义与父类同名的方法或属性,则会自动覆盖父类对应的方法或属性:

    >>> class Child(Parent):
        def hello(self):
            print("正在调用子类的方法...")
    
            
    >>> c = Child()
    >>> c.hello()
    正在调用子类的方法...

    好,那尝试一下刚才提到的金鱼(Goldfish)、三文鱼(Salmon)、鲤鱼(Carp),还有鲨鱼(Shark)的例子:

    #p11-2.py
    import random as r
    
    class Fish:
          def __init__(self):
                self.x = r.randint(0, 10)
                self.y = r.randint(0, 10)
                
          def move(self):
                #这里主要演示类的继承机制,就不考虑检查场景边界和移动方向的问题
    
                #假设所有鱼都是一路向西游
                self.x -= 1
                print("我的位置是:", self.x, self.y)
    
    class Goldfish(Fish):
          pass
    
    class Carp(Fish):
          pass
    
    class Salmon(Fish):
          pass
    
    #上边几个都是食物,食物不需要有个性,所以直接继承Fish类的全部属性和方法即可
    #下边定义鲨鱼类,这个是吃货,除了继承Fish类的属性和方法,还要添加一个吃的方法
    
    class Shark(Fish):
          def __init__(self):
                self.hungry = True
    
          def eat(self):
                if self.hungry:
                      print("吃货的梦想就是天天有的吃^_^")
                      self.hungry = False
                else:
                      print("太撑了,吃不下了!")
    >>> #先运行p11-2.py
    >>> fish = Fish()
    >>> #试试小鱼儿能不能动
    >>> fish.move()
    我的位置是: 7 0
    >>> goldfish = Goldfish()
    >>> goldfish.move()
    我的位置是: 2 2
    >>> goldfish.move()
    我的位置是: 1 2
    >>> goldfish.move()
    我的位置是: 0 2
    >>> #可见金鱼确实在一路向西
    >>> #下面尝试生成鲨鱼
    >>> shark = Shark()
    >>> #试试这货能不能吃东西?
    >>> shark.eat()
    吃货的梦想就是天天有的吃^_^
    >>> shark.eat()
    太撑了,吃不下了!>>> shark.move()
    Traceback (most recent call last):
      File "<pyshell#17>", line 1, in <module>
        shark.move()
      File "C:\Users\14158\Desktop\lalallalalal.py", line 13, in move
        self.x -= 1
    AttributeError: 'Shark' object has no attribute 'x'

    奇怪!同样是继承于Fish类,为什么金鱼(goldfish)可以移动,而鲨鱼(shark)一移动就报错呢?

    其实这里抛出的异常说得很清楚了:Shark对象没有x属性。原因其实是这样的:在Shark类中,重写了魔法方法_ _init_ _,但新的_ _int_ _方法里边没有初始化鲨鱼的x坐标和y坐标,因此调用move方法就会出错。那么解决这个问题的方案就很明显了,应该在鲨鱼类中重写_ _int_ _方法的时候先调用基类Fish的_ _init_ _方法。

    下面介绍两种可以实现的技术:

    (1)调用未绑定的父类方法

    (2)使用super函数

     

    *******************************

    二、调用未绑定的父类方法

    *******************************

     看以下代码:

    class Shark(Fish):
          def __init__(self):
                Fish.__init__(self)
                self.hungry = True

    再运行下,发现鲨鱼可以移动了:

    >>> #先运行修改后的p11-2.py
    >>> shark = Shark()
    >>> shark.move()
    我的位置是: 6 0
    >>> shark.move()
    我的位置是: 5 0
    >>> shark.move()
    我的位置是: 4 0

    这里需要注意的是这个self并不是父类Fish的实例对象,而是子类Shark的实例对象,所以这里说的未绑定是指并不需要绑定父类的实例对象,使用子类的实例对象代替即可。

    在Python中有一个更好的方案可以代替它,就是使用super函数。

     

    **********************

    三、使用super函数

    **********************

     super函数能够帮我们自动找到基类的方法,而且还为我们传入了self参数,这样就不需要做这些事了:

    class Shark(Fish):
          def __init__(self):
                super().__init__()
                self.hungry = True

    运行后会得到同样的结果。

    super函数的“超级”之处在于你不需要明确给出任何基类的名字,它会自动帮你找到所有基类以及对应的方法。由于你不用给出基类的名字,这就意味着如果需要改变类继承关系,只要改变class语句里的父类即可,而不必要在大量代码中去修改所有被继承的方法。

     

    ****************

    四、多重继承

    ****************

     除此之外Python还支持多继承,就是可以同时继承多个父类的属性和方法:

    class 类名(父类1,父类2,父类3,...):
            ...
    >>> class Base1:
        def foo1(self):
            print("我是foo1,我在Base1中...")
    
            
    >>> class Base2:
        def foo2(self):
            print("我是foo2,我在Base2中...")
    
            
    >>> class C(Base1,Base2):
        pass
    
    >>> c = C()
    >>> c.foo1()
    我是foo1,我在Base1中...
    >>> c.foo2()
    我是foo2,我在Base2中...

    上面就是基本的多重继承的语法。但多重继承其实很容易导致代码混乱,所以当你不确定是否真的必须使用多重继承的时候,请尽量避免使用它,因为有些时候会出现不可预见的BUG。

     

    【扩展阅读】多重继承的陷阱:砖石继承(菱形继承)问题(https://fishc.com.cn/forum.php?mod=viewthread&tid=48759&extra=page%3D1%26filter%3Dtypeid%26typeid%3D403).

    *******************************

    五、课时38课后习题及答案

    *******************************

     

     

关键字