python教程之类的继承(Class Inheritance)

  1. 类的继承(Class Inheritance)

  2. 前面我们定义了类,用类生成了对象。对象能接收数据及输出结果。在具体的编程中,主要考虑程序中会有什么对象,如何让对象之间进行数据交互,这是一种叫面向对象编程的方法。要学好这种方法,还要学习另一个非常重要的概念,叫做继承。俗话说,种瓜得瓜,种豆得豆,老鼠儿子会打洞。它们说的都是一种继承的关系。有继承,就会有变异,随着环境的改变,岁月的变迁,老鼠儿孙们可能会不断变异从而形成新的物种。现代计算机语言中,面向对象编程虽然是更加抽象的编程方法,但是我们也没必要想得那么复杂,在海龟画图中我们就不断地和海龟类打交道。由于在Python中类和类之间也会有继承关系,所以通过继承Turtle类,我们可以定义新的类, 让海龟摇身一变成“人”类也是完全有可能的。假设有一个Fruit(水果)类,我们再定义一个Apple类,让它继承自Fruit类,那么Fruit就是它的父类,也称基类或超类,相应的Apple称为Fruit的子类。子类会继承父类的属性和方法,当然也可以定义新的方法,如果子类定义了和父类同名的方法,这叫做重写父类的方法。下面是Fruit类的代码:
  3. >>> class Fruit:                  # 定义名为Fruit的类
         def __init__(self,w,c):      # 初始化方法
             self.weight = w          # 设定初始重量
             self.color = c          # 设定初始颜色
         def addweight(self,w):      
             self.weight += w        # 将重量增加w
         def getweight (self):  
             return self.weight     # 返回self的重量
    

    所有的水果都有重量,有颜色,上面的weight属性代表水果的重量,color属性代表水果的颜色。addweight方法能让水果的重量增加,getweight方法用来返回水果的重量。接下来定义一个叫Apple的类,让它继承自Fruit类。

  4. >>> class Apple(Fruit):                # 定义名为Apple的类,它继承自Fruit
         def __init__(self,w,c,shape):     # 初始化方法
            Fruit.__init__(self,w,c)       # 首先运行父类的初始化方法
            self.shape = shape             # 新增形状属性,并赋值
         def getshape(self):      
           return self.shape               #  返回形状
    

    上面的第1行代码定义了一个名为Apple(苹果)的类,它继承自Fruit类。语法就是在类的名字后面写上小括号,小括号里写上父类的名字即可。在苹果类的初始化方法中,我们调用了父类的初始化方法,注意,这是必需的,一般是写在初始化方法的第一行。父类的初始化方法对重量和颜色进行了赋值,然后就是第4行代码,它对shape(形状)进行了赋值。

    如果对第4行代码:self.shape = shape比较迷惑的话,首先要知道在self的点后面的名字是我们自定义的属性,这个属性的名称叫shape。而在等于号后面的shape是给这个属性赋的值,它是从方法的参数列表中来的。下面是实例化Apple类时的赋值图。

  5. python类的实例化
  6. 第5行代码定义了一个方法,它直接返回对象的形状。由于苹果类继承自水果类,所以它也有addweight方法和getweight方法。注意,实例化苹果类时,它有三个参数。以下是代码:
  7. >>> a = Apple(50,"red","circle")         # 实例化一个苹果
    >>> a.addweight(100)                 # 增重100
    >>> print(a.getweight())                # 打印重量
    >>> print(a.getshape())                # 打印形状
    

    类的继承示例一(Inheritance Example one)

  8.  上面说了类和类之间有继承的关系,我们学了海龟模块,每个海龟都是通过Turtle类来生成的。那么我们定义的类能不能继承自Turtle类呢?这当然是可以的,假设我们有一些海龟对象,我们并不需要它们画画,并且希望它们的形状都是圆形,那么我们应该把它们归为一类,假设叫Ball,以下是Ball的代码。
  9. >>> from turtle import Turtle                          # 从海龟模块导入Turtle类
    >>> class Ball(Turtle):                                # 定义球类,继承自Turtle类
      def __init__(self):
        Turtle.__init__(self,shape='circle')     # 首先调用父类的初始化方法
        self.penup()                        # 由于不需要画画,所以让它抬笔
      def move(self,dx,dy):                       # 定义移动方法
        x = self.xcor() + dx                # 水平坐标增dx
        y = self.ycor() + dy                # 垂直坐标增dy
        self.goto(x,y)                      # 坐标定位
    
        
    >>> ball = Ball()                       #  实例化一个小球
    >>> while True:
      ball.move(1,1)                         # 让小球每次在水平和垂直方向上移动一个单位
    

    由于Ball类是继承自Turtle类,所以它就像Turtle类的亲生儿子一样,继承了海龟所有的方法和属性。在第5 行代码中,由于海龟类的对象默认是落笔的,但我们并不希望它的儿子Ball类画画,所以把它给抬笔了。

    在第6行定义了一个叫move的方法。它的作用是让对象在水平和垂直方向上位移。 第12行代码是生成了一个ball,它不断地调用move方法,每次在水平和垂直方向上都移动1个单位。结果就是运行程序的时候看到一个小球朝右上角移动。通过定义move方法,好像Ball类相对于Turtle类发生了变异一样。

  1. 类的继承示例二(Inheritance Example two)

  2.  上面我们定义了一个Ball类,它有移动的方法,可是球移动一会儿就不见了。接下来我们定义一个弹球类,让它可以“碰到边缘就反弹”。我们让弹球类继承自球类。重新编写它的move方法实现这个功能。代码如下所示:
  3. from turtle import Turtle
    from random import randint
    class Ball(Turtle):    
        def __init__(self):
            Turtle.__init__(self,shape='circle')
            self.penup()
            self.sw = self.screen.window_width()       # 定义sw属性,它的值是屏幕的宽度
            self.sh = self.screen.window_height()       # 定义sh属性,它的值是屏幕的高度        
        def move(self,dx,dy):
            x = self.xcor() + dx
            y = self.ycor() + dy
            self.goto(x,y)	    
    class BounceBall(Ball):                            # 定义BounceBall类,它继承自Ball类  
        def __init__(self):
            Ball.__init__(self)                         # 调用父类的初始化方法
            self.xspeed = randint(-5,5)                 # 设定xspeed属性,代表每次的水平位移 
            self.yspeed = randint(-5,5)                 # 设定yspeed属性,代表每次的垂直位移
        def move(self):        
            x = self.xcor() + self.xspeed                # 对象的x坐标加上水平位移
            y = self.ycor() + self.yspeed                # 对象的y坐标加上垂直位移
            self.goto(x,y)                             # 定位到新的位置
            if x > self.sw//2 or x < -self.sw//2:      # 如果x坐标大于或小于屏幕宽度的一半
                self.xspeed = -self.xspeed              # 水平位移取负
            if y > self.sh//2 or y <  -self.sh//2:      # 如果y坐标大于或小于屏幕高度的一半
                self.yspeed = -self.yspeed              # 垂直位移取负
    ball = BounceBall()
    while True:
        ball.move()       
    

    上面的代码运行的时候,我们能看到一个黑色的小球,它碰到窗口的边框会反弹,这是由于我们在BounceBall类的定义中对move方法进行了重写。我们把小球看成一个点,当小球的坐标大于或等于屏幕宽度的一半的时候,我们让它的xspeed取反,这样它在水平方向上就会朝相反的方向移动了。垂直反弹的原理也是一样的。在这里反弹原理不是重点,重点就是我们可以重新改写父类的方法,并且在有些情况下要先调用父类的同名方法。如果要让一个方法失效,也可以通过改写父类的方法,只要定义同名方法,把它的语句组换成pass就行了。

    通过继承某类,我们不必编写重复的代码,只要把缺少或需要优化的功能重新编写就行了。子类可以重新编写父类的方法,这也叫做方法重载。