Created
July 28, 2014 01:14
-
-
Save numbbbbb/364c8e14a754795f501a to your computer and use it in GitHub Desktop.
翻译
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
用面向对象的术语来说,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