7.3.1 继承

不难理解,子类拥有超类的一切特性,凡是超类适用的地方,子类也适用。例如,“研究 生”具有“学生”的全部属性,包括数据属性(如学号、姓名、年龄)和行为属性(如选课、 参加学生社团等),凡是“学生”能做的,“研究生”都能做。子类拥有超类的全部属性(数 据和方法),这是面向对象方法中极为重要的一个特色,称为继承(inheritance)。

子类除了继承超类的属性,还包含一些自己的特殊属性。例如,“研究生”具有导师信 息,而一般的“学生”未必有导师。在进行面向对象设计时,一般先定义超类,然后在超类 基础上通过添加一些特殊属性来定义子类。这种定义方式下,子类中不必重复定义那些继承 来的属性,从而简化了子类定义。这也是继承机制带来了的另一个重要特色——代码重用(code reuse),即超类中的代码可以通过继承机制被子类重复使用。当我们需要定义一个新 类时,如果发现它与某个现有的类在很多方面都相同,那么就无需重新写代码来实现这些相 同行为,而只需继承现有功能。

① 超类/子类也称为基类(base class)/派生类(derived class)。

Python 中定义子类采用如下形式:

class <子类名>(<超类名>):
    <特殊属性 1>
    ...
    <特殊属性 n>

注意,超类与子类的定义一般置于同一个模块中。如果超类在另一个模块中定义,则定义子 类时必须指明模块信息,形如:

class <子类名>(<模块名>.<超类名>):
    ...

下面通过具体例子来说明超类、子类以及继承概念。程序 7.3 中定义了一个 Person 类, 它在最一般的层次上刻划了“人”对象:每个人有姓名和出生年份数据,并且能回答外界提 出的“叫什么名字”、“今年多大了”之类的问题。为便于阅读、比较,我们将 Person 类的定 义复制于此:

class Person:
def __init__ (self,n,y): 
    self.name = n 
    self.year = y
def whatName(self):
    print "My name is",self.name 
def howOld(self,y):
    age = y – self.year 
    if age > 0:
        print "My age in",y,"is",age 
    else:
        print "I was born in",self.year

下面以 Person 作为超类,来定义几种具有特殊属性(数据和行为)的人。 首先定义“学生”。学生是人,因此拥有 Person 类的所有信息。学生还具有一些特殊属性,比如学校、学号信息。按照子类的定义方式,我们可以定义如下的 Student 类:

class Student(Person):
    def __init__ (self,n,y,u,id): 
        Person.__init__ (self,n,y) 
        self.univ = u
        self.snum = id 
    def getUniv(self):
        return self.univ 
    def getNum(self):
        return self.snum

Student 类定义的第一行表明,Student 类是 Person 类的子类。作为特殊的人,学生拥有 普通人不一定有的 self.univ(学校)和 self.snum(学号)数据,因此 Student 类的构造器与 Person 不同,需要四个初始参数:姓名、出生年份、学校和学号。创建 Student 对象时要进 行的初始化工作包括:首先作为 Person 对象要执行 Person 对象的初始化,即 Person.__init (); 然后再执行 Student 对象特有的初始化工作,即对 self.univ 和 self.snum 两个实例变量进行赋 值。可见,子类的构造器一般是在超类构造器的基础上另外执行一些初始化工作。Student 对象除了能响应 Person 对象都能响应的 whatName 和 howOld 之外,还具有两个特有的方法: getUniv 和 getNum。 我们再来定义另一种特殊的人——教师。假设教师拥有指导的学生人数信息,以及设置和获取这个信息的方法,则可定义 Teacher 类如下:

class Teacher(Person): 
    def setNum(self,n):
        self.snum = n 
    def getNum(self):
        return self.snum

Teacher 类没有定义自己的初始化方法 init,因此创建 Teacher 对象时将自动调用超类 Person 的 init方法来进行初始化。Teacher 对象有一个特殊的实例变量 num,但其值不是 在创建对象时初始化的,而是在创建之后利用 setNum 方法来设置。不要忘了,Python 对象 的实例变量可以在任何方法中定义,可以在任何时候赋值。

定义了以上各种类之后,就可以编写使用这些类的程序了。假设 Person、Student 和 Teacher 类定义都存储在模块文件 person.py 之中,下面以交互方式演示对这些类的使用:

>>> from person import *
>>> tom = Student("Tom",1995,"SJTU","S001")
>>> tom.whatName()
My name is Tom
>>> tom.howOld(2013)
My age in 2013 is 18
>>> tom.getUniv()
'SJTU'
>>> print tom.getNum()
S001
>>> huck = Teacher("Huck",1975)
>>> huck.whatName()
My name is Huck
>>> huck.howOld(2013)
My age in 2013 is 38
>>> huck.setNum(8)
>>> print huck.getNum()
8
>>> lucy = Person("Lucy",2005)
>>> lucy.getUniv()
Traceback (most recent call last):
File "&lt;pyshell#4&gt;", line 1, in &lt;module&gt; p.getUniv()
AttributeError: Person instance has no attribute 'getUniv'

子类继承超类的所有属性,因此当创建了 Student 对象 tom 后,就可以向 tom 发消息 whatName 和 howOld,tom 对象能够正确地响应这两个消息。当然还可以向 tom 发送 getUniv 和 getNum 消息,这两个方法是 Student 特有的,tom 自然能做出响应。Teacher 对象 huck 的行为也是类似的。注意,超类的实例并不具有子类中特殊属性,因此上例中向 Person 对象 lucy发送 getUniv 消息,将导致错误。