《编程导论》 CH8 类和面向对象编程
8.1 抽象的数据类型和类
抽象的数据类型vs接口
降低改动程序的复杂度->解耦&抽象
Python中使用类来实现数据抽象
例子:intSet类的定义来实现一个简单的整数集合抽象
class IntSet(object): #类开头是class关键字;IntSet是object的子类
"""intSet is a group of integer"""
def __init__(self): #类被实例化是会调用类中定义的__init__方法,init前后各两根_
"""create a blank int list"""
self.vals = []
def insert(self,e):
"""assume e is an integer, insert e into self"""
if not e in self.vals:
self.vals.append(e)
def member(self,e):
"""assume e is an integer,
if e in self return true, else return false"""
return e in self.vals
def remove(self,e):
"""assume e is an integer, delete e from self,
if e is not in self throw ValueError exception"""
try:
self.vals.remove(e)
except:
raise ValueError(str(e) + ' not found')
def getMember(self):
"""return a list which contains elements in self.
the order of the elements cannot be assumed."""
return self.val[:]
def __str__(self): #当使用print语句时,和类关联的__str__函数会被自动调用
"""return the string form of self"""
self.vals.sort()
result = ''
for e in self.vals:
result = result + str(e) + ','
return '{'+result[:-1]+'}' #-1 omits trailing comma
代码: print type(IntSet),type(IntSet.insert)
会输出:
类中出现函数定义,这个函数会被称为方法并和这个类关联起来。->方法属性 vs 数据属性(vals)
类支持两类操作:
实例化:用来创建类的实例。如语句S=IntSet()会创建一个类型为IntSet的新对象,此新对象称为IntSet的一个实例。
属性引用:使用点标记法来访问类所关联的属性。如,s.member指的是IntSet的实例s所关联的方法member。
可以使用点标记法来调用和类实例关联的方法,如:
s=IntSet()
s.insert(3)
print s.member(s)
不应把类和类的实例搞混。属性既可以关联到类本身,也可以关联到类的实例:
方法属性定义在类定义中,如IntSet.member是类IntSet的一个属性。当类被实例化时,例如通过s=IntSet(),实例的属性(如s.member)会被创建。IntSet.member和s.member是不同的对象。在IntSet类的定义中,s.member最初被绑定到member方法上,但是在计算的过程中可以改变这一绑定。
当数据属性被关联到类时称为类变量,被关联到实例时称为实例变量。
表现的独立性->一个抽象类型的实现包含:
·类型方法的实现
·用来表示类型值的数据结构
·关于方法如何使用数据结构的约定->表示的不变性->定义如何通过数据属性表示合法的抽象值
1.使用抽象的数据类型来设计程序
数据抽象鼓励程序设计者关注数据对象的中心性,而非关注函数。
2.使用类来记录学生和教师
import datetime
class Person(object):
def __init__(self,name):
"""create a preson"""
self.name = name
try:
lastBlank = name.rindex(' ')
self.lastName = name[lastBlank+1:]
except:
self.lastName = name
self.birthday = None
def getName(self):
"""return the person's name"""
return self.name
def getLastName(self):
"""return the person's last name"""
return self.lastName
def setBirthday(self,birthDate):
"""assume the type of birthDate is datetime.date
set the person's birthday as birthDate"""
self.birthday = birthDate
def getAge(self):
"""return the relevant number of days of the person's age"""
if self.birthday == None:
raise ValueError
return (datetime.date.today()-self.birthday).days
def __lt__(self,other): #重载小于符号
"""if the dictionary order of the person's name is less than the other's return true,
else return false"""
if self.lastName == other.lastName:
return self.name < other.lastName
return self.lastName < other.lastName
def __str__(self):
"""return the person's name"""
return self.name
me = Person('Michael Guttag')
him = Person('Barack Hussein Obama')
her = Person('Madonna')
print him.getLastName()
him.setBirthday(datetime.date(1961,8,4))
her.setBirthday(datetime.date(1958,8,16))
print him.getName(),'is',him.getAge(),'days old'
pList = [me,him,her]
for p in pList:
print p
pList.sort()
for p in pList:
print p
class MITPerson(Person):
nextIdNum = 0 #id number
def __init__(self,name):
Person.__init__(self,name)
self.idNum = MITPerson.nextIdNum
MITPerson.nextIdNum += 1
def getIdNum(self):
return self.idNum
def __lt__(self, other):
return self.idNum < other.idNum
·添加新属性
·覆盖超类的属性
1. 多层继承
Python使用保留字pass作为类的代码部分,这个类中只有从超类继承来的属性。
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
p5=Grad('Buzz Aldrin')
p6=('Billy Beaver',1984)
print p5,'is a graduate student is',type(p5) == Grad
print p5,'is an undergraduate student is',type(p5)==UG
结合上面的类,继承关系可以如下描述:
Person-MITPerson-Student-UG
\ G
中间类型Student的作用很巧妙。可以向MITPerson类添加下面的方法:
def isStudent(self):
return isinstance(self,Student)
isinstance是Python的内建函数。isinstance的第一参数可以是任意对象,但是第二个参数必须是type类型的对象。当且仅当第一个参数是第二个参数的实例时,函数返回True。如isinstance([1,2],list)的值是true。
替代法则
使用子类来定义类型的层次结构时,子类型应当被看作父类型行为的拓展。可以通过添加新属性或者覆盖继承自超类的属性来实现。
子类覆盖父类的方法时要小心,超类的重要行为一定要被所有子类支持。如果用户的代码使用超类的一个实例能正确运行,那么使用子类的实例替换掉超类的实例之后仍然应可以正常工作。
8.3 封装和信息隐藏
下面的代码可以用来记录一组学生的成绩。
class Grades(object):
def __init__(self):
self.students=[]
self.grades={}
self.isSorted=True
def addStudent(self,student):
if student in self.students:
raise ValueError('Duplicate student')
self.students.append(student)
self.grades[student.getIdNum()]=[]
self.isSorted = False
def addGrade(self,student,grade):
try:
self.grades[student.getIdNum()].append(grade)
except:
raise ValueError('Student not in mapping')
def getGrades(self,student):
try:
return self.grades[student.getIdNum()][:] #返回成绩副本,避免意料之外的改动
except:
raise ValueError('Student not in mapping')
def getStudents(self):
if not self.isSorted:
self.students.sort()
self.isSorted = True
return self.students[:]
def gradeReport(course):
report = ''
for s in course.getStudents():
tot=0.0
numGrades = 0
for g in course.getGrades(s):
tot+=g
numGrades += 1
try:
average = tot/numGrades
report = report + '\n' +str(s)+'\'s mean grade is '+str(average)
except ZeroDivisionError:
report = report + '\n'+str(s)+' has no grades'
return report
ug1 =UG('Jane Doe',2014)
ug2 =UG('John Doe',2015)
ug3 =UG('David Henry',2003)
g1 =Grad('Billy Bucker')
g2 =Grad('Bucky F.Dent')
sixHundred=Grades()
sixHundred.addStudent(ug1)
sixHundred.addStudent(ug2)
sixHundred.addStudent(g1)
sixHundred.addStudent(g2)
for s in sixHundred.getStudents():
sixHundred.addGrade(s,75)
sixHundred.addGrade(g1,25)
sixHundred.addGrade(g2,100)
sixHundred.addStudent(ug3)
print gradeReport(sixHundred)
封装:封装是指将数据属性和操作它们的方法捆绑在一起。如:Rafael=MITPerson() 可以使用点标记法来访问Rafael的属性,比如年龄和学号。
信息隐藏:这是模块化的关键。Java&C++等编程语言提供了可以强制隐藏信息的方法。程序猿可以控制类中数据属性的可见性,因此可以强制要求用户代码只能通过对象的属性来访问数据。然而Python没有提供强制隐藏信息的方法。因此无法严格地控制类实例属性的访问。
1.生成器
数据隐藏->性能损失
看上面代码中的gradeReport。调用course.allStudents会创建并返回一个大小为n的列表,n代表学生数量。创建一个已有列表的副本非常低效。一种方法是放弃抽象并允许gradeReport直接访问实例变量course.students,但是这会破坏信息隐藏。幸好还有另一种方法:yield
改进版getStudents:
def getStudents(self):
if not self.isSorted:
self.students.sort()
self.isSorted=True
for s in self.students:
yield s
在for循环的第一次迭代开始时,解释器会执行生成器内部的代码。它会运行到第一个yield语句然后返回yield语句中表达式的值。在下一次迭代时,生成器会从上次返回的位置继续执行,本地变量的绑定也和上次执行时一样。解释器会一直运行到下一个yield语句然后再次跳出。循环会在执行完所有代码或者遇到return语句时退出。
上面的getStudents可以让程序员用for循环来遍历Grades类型对象中的所有学生,就像遍历列表等内建类型的元素一样。举例来说,代码:
book = Grades()
book.addStudent(Grad('Julie'))
book.addStudent(Grad('Charlie'))
for s in book.getStudents():
print s
会输出:
Julie
Charlie
无论getStudents是一次返回一个列表还是一次生成一个值,for循环都可以进行遍历。一次生成一个值效率更高,因为这样不需要创建新列表。
8.4 进阶实例:抵押贷款
下面的代码比较了三种不同类型的抵押贷款
__author__ = 'sunzhaoyue'
def findPayment(loan,r,m):
"""assume:loan and r are float,m is an integer
return the money should be refunded when the num of money loaned is loan,
monthly interest is r in total m months"""
return loan*((r*(1+r)**m)/((1+r)**m-1))
class Mortgage(object):
"""an abstract class to construct different types of mortgage"""
def __init__(self,loan,annRate,months):
self.loan = loan
self.rate = annRate/12.0
self.months = months
self.paid = [0.0]
self.owed = [loan]
self.payment = findPayment(loan,self.rate,months)
self.legend = None #description of mortage
def makePayment(self):
"""refund"""
self.paid.append(self.payment)
reduction = self.payment - self.owed[-1]*self.rate
self.owed.append(self.owed[-1] - reduction)
def getTotalPaid(self):
"""return num of money has been repaid now"""
return sum(self.paid)
def __str__(self):
return self.legend
class Fixed(Mortgage):
def __init__(self,loan,r,months):
Mortgage.__init__(self,loan,r,months)
self.legend = 'Fixed, '+str(r*100)+ '%'
class FixedWithPts(Mortgage):
def __init__(self,loan,r,months,pts):
Mortgage.__init__(self,loan,r,months)
self.pts = pts
self.paid = [loan*(pts/100.0)]
self.legend = 'Fixed, '+str(r*100)+'%, '+str(pts)+' points'
class TwoRate(Mortgage):
def __init__(self,loan,r,months,teaserRate,teaserMonths):
Mortgage.__init__(self,loan,teaserRate,months)
self.teaserMonths = teaserMonths
self.teaserRate = teaserRate
self.nextRate = r/12.0
self.legend = str(teaserRate*100) + '% for'+str(self.teaserMonths)+' months, then'+str(r*100)+'%'
def makePayment(self):
if len(self.paid)==self.teaserMonths+1:
self.rate = self.nextRate
self.payment = findPayment(self.owed[-1],self.rate,self.months-self.teaserMonths)
Mortgage.makePayment(self)
def compareMortgages(amt,years,fixedRate,pts,ptsRate,varRate1,varRate2,varMonths):
totMonths = years*12
fixed1 = Fixed(amt, fixedRate, totMonths)
fixed2 = FixedWithPts(amt, ptsRate, totMonths, pts)
twoRate = TwoRate(amt, varRate2, totMonths, varRate1, varMonths)
morts = [fixed1,fixed2,twoRate]
for m in range(totMonths):
for mort in morts:
mort.makePayment()
for m in morts:
print m
print ' Total payments = $' + str(int(m.getTotalPaid()))
compareMortgages(amt=200000, years=30, fixedRate=0.07, pts=3.25,
ptsRate=0.05, varRate1=0.045, varRate2=0.095, varMonths=48)
Fixed, 7.0%
Total payments = $479017
Fixed, 5.0%, 3.25 points
Total payments = $393011
4.5% for48 months, then9.5%
Total payments = $551444
CH9 算法复杂度简介
9.1 思考计算复杂度
时间的抽象、解决对输入的依赖
最好情况、最坏情况、平均情况
最坏情况定义了运行时间的上界
9.2 渐进表示
为了量化“大规模”,我们使用渐进表示来描述当输入规模接近无限大时,算法的复杂度。
Big O表示法
9.3 一些重要的复杂度
1.常数复杂度 O(1)
表示渐近复杂度和输入的规模无关。
2.对数复杂度O(log n) 二分查找
3.线性复杂度O(n)
4.对数线性复杂度 O(n log(n)) 归并排序
5.多项式复杂度
6.指数复杂度
7.复杂度对比
这部分由于数据结构都有介绍,有些东西都很熟悉了,就简单记下