Skip to content

Instantly share code, notes, and snippets.

@numbbbbb
Created July 28, 2014 01:14
Show Gist options
  • Save numbbbbb/364c8e14a754795f501a to your computer and use it in GitHub Desktop.
Save numbbbbb/364c8e14a754795f501a to your computer and use it in GitHub Desktop.
翻译
用面向对象的术语来说,MITPerson是Person的子类,因此继承了超类的属性。除了继承之外,子类还可以
- 添加新属性。例如,MITPerson添加了实例变量idNum和方法getIdNum。
- 覆盖超类的属性。例如,MITPerson覆盖了__init__和__lt__。
MITPerson.__init__方法首先调用Person.__init__来初始化被继承的实例变量self.name,然后它初始化了self.idNum,这个实例变量只存在于MITPerson的实例中,Person的实例中是没有的。
实例变量self.idNum使用类变量nextIdNum来初始化,这个变量属于类MITPerson,而不是类的实例。创建MITPerson的实例时并不会创建一个新的nextIdNum。这样__init__就可以确保每个MITPerson的实例都有一个独一无二的idNum。nextIdNum这样的属性被称为类变量,因为它们属于类本身,不是属于类的实例。
考虑下面代码
```
p1 = MITPerson('Barbara Beaver')
print str(p1) + '\'s id number is ' + str(p1.getIdNum())
```
第一行创建了一个MITPerson类的实例。第二行有点复杂。它试着对表达式`str(p1)`求值,运行时系统会首先检查MITPerson类是否有关联的__str__方法。没有这个方法,所以接着检查Person类是否有关联的方法。有这个方法,所以使用这个方法。当运行时系统试着对表达式`p1.getIdNum()`求值时,首先检查MITPerson类是否有关联的getIdNum方法。有这个方法,所以调用这个方法,然后打印出
```
Barbara Beaver's id number is 0
```
(还记得吧,在字符串中字符“\”是转义字符,表示下一个字符会被特殊对待。在字符串'\'s id number is '中“\”表示之后的单引号是字符串的一部分,不是字符串的分界符。)
考虑下面的代码
```
p1 = MITPerson('Mark Guttag')
p2 = MITPerson('Billy Bob Beaver')
p3 = MITPerson('Billy Bob Beaver')
p4 = Person('Billy Bob Beaver')
```
我们创建了四个虚拟的人物,它们中的三个人名字是Billy Bob Beaver。其中的两个是MITPerson类型,另一个仅仅是Person类型。如果我们执行下面的代码
```
print 'p1 < p2 =', p1 < p2
print 'p3 < p2 =', p3 < p2
print 'p4 < p1 =', p4 < p1
```
解释器会打印出
```
p1 < p2 = True
p3 < p2 = False
p4 < p1 = True
```
因为p1、p2和p2都是MITPerson类型,解释器在对前两个比较进行求值的时候会使用MITPerson类中定义的__lt__方法,所以顺序由id决定。在第三个比较中,<的操作数是不同类型。由于表达式的第一个参数决定了调用哪个__lt__方法,p4 < p1和p4.__lt__(p1)是相同的。因此解释器使用和p4的类型Person关联的<操作符,也就是用名字来进行排序。
如果我们试着执行
```
print 'p1 < p4 =', p1 < p4
```
解释器会调用和p1的类型关联的__lt__操作符,在本例中就是MITPerson中定义的__lt__函数。这行代码会抛出异常
```
AttributeError: 'Person' object has no attribute 'idNum'
```
因为p4绑定的对象没有属性idNum。
###8.2.1 多层集成
图8-4向类的层次结构中添加了两层。
```
class Student(MITPerson):
pass
class UG(Student):
def __init__(self, name, classYear):
MITPerson.__init__(self, name)
self.year = classYear
def getClass(self):
return self.year
class Grad(Student):
pass
```
{-:-}图8-4 两类学生
添加UG类似乎顺理成章,因为我们想给每个本科生关联一个毕业年份(或者是预计毕业的年份)。但是Student和Grad类是干什么的?由于使用Python的保留字pass作为类的代码部分,这个类中只有从超类继承来的属性。为什么会有人创建一个没有新属性的类呢?
通过添加Grad类,我们可以创建两类不同的学生并使用他们的类型来区分两个对象。例如,下面的代码
```
p5 = Grad('Buzz Aldrin')
p6 = UG('Billy Beaver', 1984)
print p5, 'is a graduate student is', type(p5) == Grad
print p5, 'is an undergraduate student is', type(p5) == UG
```
会打印出
```
Buzz Aldrin is a graduate student is True
Buzz Aldrin is an undergraduate student is False
```
中间类型Student的作用很巧妙。考虑一下向MITPerson类添加下面的方法
```
def isStudent(self):
return isinstance(self, Student)
```
isinstance是Python的内建函数。isinstance的第一个参数可以是任意对象,但是第二个参数必须是type类型的对象。函数会返回True当且仅当第一个参数是第二个参数的实例。例如,`isinstance([1,2], list)`的值是True。
回到我们的例子中,下面的代码
```
print p5, 'is a student is', p5.isStudent()
print p6, 'is a student is', p6.isStudent()
print p3, 'is a student is', p3.isStudent()
```
会打印出
```
Buzz Aldrin is a student is True
Billy Beaver is a student is True
Billy Bob Beaver is a student is False
```
注意`isinstance(p6, Student)`和`type(p6) == Student`是不同的。p6绑定的对象类型是UG,不是Student,但是因为UG是Student的子类,所以p6绑定的对象是Student类的实例。
由于现在只有两类学生,所以isStudent可以这样实现
```
def isStudent(self):
return type(self) == Grad or type(self) == UG
```
然而,如果之后添加了一个新的学生类型,就需要对isStudent进行修改。通过创建中间类Student并使用isinstance,我们可以避免这个问题。例如,如果我们添加了下面的类
```
class TransferStudent(Student):
def __init__(self, name, fromSchool):
MITPerson.__init__(self, name)
self.fromSchool = fromSchool
def getOldSchool(self):
return self.fromSchool
```
我们并不需要修改isStudent。
在程序的开发和维护过程中向旧类中添加新类或者新方法并不罕见。优秀的程序员会把代码设计得很好,当
发生类似情况的时候只需要修改最少的代码。
###8.2.2 替代法则
使用子类来定义类型的层次结构时,子类型应当被看做父类行行为的扩展。可以通过添加新属性或者覆盖继承自超类的属性来实现。例如,TransferStudent类型对Student进行了扩展,添加了对于之前学校的标记。
有时候,子类会覆盖父类的方法,此时一定要小心谨慎。尤其是,超类的重要行为一定要被所有子类支持。如果用户的代码使用超类的一个实例,那使用子类的实例替换掉超类的实例之后仍然可以正常工作。举例来说,使用Student实例的代码也应当可以使用TransferStudent的实例。这被称为替代法则。[39]
--------------
[39] 这个法则由Barbara Liskov和Jeannette Wing在1994年的论文“子类的行为概念(A behavioral notion of subtyping)”中第一次清楚地定义。
相反地,不应当期望使用TransferStudent的代码换成Student也可以正常工作。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment