-
类的继承(Class Inheritance)
- 前面我们定义了类,用类生成了对象。对象能接收数据及输出结果。在具体的编程中,主要考虑程序中会有什么对象,如何让对象之间进行数据交互,这是一种叫面向对象编程的方法。要学好这种方法,还要学习另一个非常重要的概念,叫做继承。俗话说,种瓜得瓜,种豆得豆,老鼠儿子会打洞。它们说的都是一种继承的关系。有继承,就会有变异,随着环境的改变,岁月的变迁,老鼠儿孙们可能会不断变异从而形成新的物种。现代计算机语言中,面向对象编程虽然是更加抽象的编程方法,但是我们也没必要想得那么复杂,在海龟画图中我们就不断地和海龟类打交道。由于在Python中类和类之间也会有继承关系,所以通过继承Turtle类,我们可以定义新的类, 让海龟摇身一变成“人”类也是完全有可能的。假设有一个Fruit(水果)类,我们再定义一个Apple类,让它继承自Fruit类,那么Fruit就是它的父类,也称基类或超类,相应的Apple称为Fruit的子类。子类会继承父类的属性和方法,当然也可以定义新的方法,如果子类定义了和父类同名的方法,这叫做重写父类的方法。下面是Fruit类的代码:
-
>>> 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类。
-
>>> 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行代码定义了一个方法,它直接返回对象的形状。由于苹果类继承自水果类,所以它也有addweight方法和getweight方法。注意,实例化苹果类时,它有三个参数。以下是代码:
-
>>> a = Apple(50,"red","circle") # 实例化一个苹果 >>> a.addweight(100) # 增重100 >>> print(a.getweight()) # 打印重量 >>> print(a.getshape()) # 打印形状
类的继承示例一(Inheritance Example one)
- 上面说了类和类之间有继承的关系,我们学了海龟模块,每个海龟都是通过Turtle类来生成的。那么我们定义的类能不能继承自Turtle类呢?这当然是可以的,假设我们有一些海龟对象,我们并不需要它们画画,并且希望它们的形状都是圆形,那么我们应该把它们归为一类,假设叫Ball,以下是Ball的代码。
-
>>> 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类发生了变异一样。
-
类的继承示例二(Inheritance Example two)
- 上面我们定义了一个Ball类,它有移动的方法,可是球移动一会儿就不见了。接下来我们定义一个弹球类,让它可以“碰到边缘就反弹”。我们让弹球类继承自球类。重新编写它的move方法实现这个功能。代码如下所示:
-
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就行了。
通过继承某类,我们不必编写重复的代码,只要把缺少或需要优化的功能重新编写就行了。子类可以重新编写父类的方法,这也叫做方法重载。