python 学习简记 《编程导论》 CH8&CH9

《编程导论》 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

定义一个类会创建一个type类型的对象并给它关联一些instancemethod(实例方法)类型的对象。如,表达式IntSet.insert引用的是IntSet类中定义的insert方法。

代码: 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)

表达式中点号前面的对象会被当作第一个参数传入方法。惯例用self来命名绑定到这个实参的形参。


不应把类和类的实例搞混。属性既可以关联到类本身,也可以关联到类的实例:

方法属性定义在类定义中,如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

8.2 继承

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

子类(MITPerson)继承了超类(Person)的属性,子类可以:

·添加新属性

·覆盖超类的属性

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[:]

下面的函数使用Grades类生成6.00这门课的学生成绩列表。

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

包含yield语句的函数定义会被特殊对待。yield语句会告诉Python函数是一个生成器。生成器通常和for语句一起使用。(要真正理解生成器,需要理解Python内建迭代器的实现方法)。

在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.复杂度对比


这部分由于数据结构都有介绍,有些东西都很熟悉了,就简单记下

你可能感兴趣的:(python 学习简记 《编程导论》 CH8&CH9)