1、面向对象程序设计(Object Oriented Programming,OOP)主要针对大型软件设计而提出,使得软件设计更加灵活,能够很好地支持代码复用和设计复用,并且使得代码具有更好的可读性和可扩展性。面向对象程序设计的一条基本原则是计算机程序由多个能够起到子程序作用的单元或对象组合而成,大大地降低了软件开发的难度。面向对象程序设计的一个关键性观念是将数据以及对数据的操作封装在一起,组成一个相互依存、不可分割的整体,即对象。对于相同类型的对象进行分类、抽象后,得出共同的特征而形成了类,面向对象程序设计的关键就是如何合理地定义和组织这些类以及类之间的关系。1面向对象程序设计面向对象程序设计P
2、ython完全采用了面向对象程序设计的思想,是真正面向对象的高级动态编程语言,完全支持面向对象的基本功能,如封装、继承、多态以及对基类方法的覆盖或重写。Python中对象的概念很广泛,Python中的一切内容都可以称为对象,除了数字、字符串、列表、元组、字典、集合、range对象、zip对象等等,函数也是对象,类也是对象。创建类时用变量形式表示的对象属性称为数据成员,用函数形式表示的对象行为称为成员方法,成员属性和成员方法统称为类的成员。2 Python使用class关键字来定义类,class关键字之后是一个空格,然后是类的名字,再然后是一个冒号,最后换行并定义类的内部实现。类名的首字母一般要
3、大写,当然也可以按照自己的习惯定义类名,但一般推荐参考惯例来命名,并在整个系统的设计和实现中保持风格一致,这一点对于团队合作尤其重要。class Car:def infor(self):print(This is a car)3n 定义了类之后,可以用来实例化对象,并通过“对象名.成员”的方式来访问其中的数据成员或成员方法。car=Car()car.infor()n 在Python中,可以使用内置方法isinstance()来测试一个对象是否为某个类的实例。isinstance(car,Car)#Trueisinstance(car,str)#False4n Python提供了一个关键字“pa
4、ss”,表示空语句,可以用在类和函数的定义中或者选择结构中。当暂时没有确定如何实现功能,或者为以后的软件升级预留空间,或者其他类型功能时,可以使用该关键字来“占位”。class A:passdef demo():passif 53:pass5类的所有实例方法都必须至少有一个名为self的参数,并且必须是方法的第一个形参(如果有多个形参的话),self参数代表将来要创建的对象本身。在类的实例方法中访问实例属性时需要以self为前缀。在外部通过对象调用对象方法时并不需要传递这个参数,如果在外部通过类调用对象方法则需要显式为self参数传值。6n 在Python中,在类中定义实例方法时将第一个参数定
5、义为“self”只是一个习惯,而实际上不必须使用“self”这个名字,尽管如此,建议编写代码时仍以self作为方法的第一个参数名字。class A:def _init_(hahaha,v):hahaha.value=v def show(hahaha):print(hahaha.value)a=A(3)a.show()37属于实例的数据成员一般是指在构造函数_init_()中定义的,定义和使用时必须以self作为前缀;属于类的数据成员是在类中所有方法之外定义的。在主程序中(或类的外部),实例属性属于实例(对象),只能通过对象名访问;而类属性属于类,可以通过类名或对象名都可以访问。在实例方法中可
6、以调用该实例的其他方法,也可以访问类属性以及实例属性。8 在Python中,可以动态地为自定义类和对象增加或删除成员,这一点是和很多面向对象程序设计语言不同的,也是Python动态类型特点的一种重要体现。9class Car:price=100000#定义类属性 def _init_(self,c):self.color=c#定义实例属性car1=Car(Red)#实例化对象car2=Car(Blue)print(car1.color,Car.price)#查看实例属性和类属性的值Car.price=110000#修改类属性Car.name=QQ#动态增加类属性car1.color=Yello
7、w#修改实例属性print(car2.color,Car.price,Car.name)print(car1.color,Car.price,Car.name)10import typesdef setSpeed(self,s):self.speed=scar1.setSpeed=types.MethodType(setSpeed,car1)#动态增加成员方法car1.setSpeed(50)#调用成员方法print(car1.speed)11 补充:补充:Python类型的动态性使得我们可以动态为自定义类及其对象增加新的属性和行为,俗称混入(mixin)机制,这在大型项目开发中会非常方便和实
8、用。例如系统中的所有用户分类非常复杂,不同用户组具有不同的行为和权限,并且可能会经常改变。这时候我们可以独立地定义一些行为,然后根据需要来为不同的用户设置相应的行为能力。12 import types class Person(object):def _init_(self,name):assert isinstance(name,str),name must be string self.name=name def sing(self):print(self.name+can sing.)def walk(self):print(self.name+can walk.)def eat(sel
9、f):print(self.name+can eat.)13 zhang=Person(zhang)zhang.sing()#用户不具有该行为AttributeError:Person object has no attribute sing zhang.sing=types.MethodType(sing,zhang)#动态增加一个新行为 zhang.sing()zhang can sing.zhang.walk()AttributeError:Person object has no attribute walk zhang.walk=types.MethodType(walk,zhang
10、)zhang.walk()zhang can walk.del zhang.walk#删除用户行为 zhang.walk()AttributeError:Person object has no attribute walk14n 在Python中,函数和方法是有区别的。方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数隐式传递过去,普通函数并不具备这个特点。15 class Demo:pass t=Demo()def test(self,v):self.value=v t.test=test t.test#普通函数 t.test(t,3)#必须为self参数传值
11、t.test=types.MethodType(test,t)t.test#绑定的方法bound method test of t.test(5)#不需要为self参数传值16 补充:补充:在Python中,变量并不直接存储值,而是存储值的引用。列表、元组、字典、集合以及其他容器类对象中的元素也是存储值的引用。对象中的成员也是存储的引用。自定义类的数据成员是该类所有对象共有的,既可以通过类访问,也可以通过该类任意对象进行访问。17 如果通过类把成员的值进行了修改,该类对象都能得到体现。然而,如果通过其中某个对象修改了value的值,不会影响类和该类其他对象。修改t1.value的值之后,t1.
12、value不再共享类的数据成员。修改T.value之后,不影响已改变的t1.value,并且t2.value仍然共享类的数据成员。18 当类成员value为列表1,2,3时,相应的一系列修改之后,内存布局如图,不管是通过类还是通过该类的对象,只要使用列表自身的原地修改方法或者下标的形式,修改的都是同一个列表。19 自定义类中的方法也遵守同样的规则。20 可以通过下面的形式访问对象所属类被暂时隐藏的成员。21Python并没有对私有成员提供严格的访问保护机制。在定义类的成员时,如果成员名以两个下划线“_”或更多下划线开头而不以两个或更多下划线结束则表示是私有成员。私有成员在类的外部不能直接访问,
13、需要通过调用对象的公开成员方法来访问,也可以通过Python支持的特殊方式来访问。公开成员既可以在类的内部进行访问,也可以在外部程序中使用。22 class A:def _init_(self,value1=0,value2=0):self.value1=value1 self._value2=value2 def setValue(self,value1,value2):self.value1=value1 self._value2=value2 def show(self):print(self.value1)print(self._value2)a=A()a.value10 a._A_v
14、alue2#在外部访问对象的私有数据成员023在IDLE、Spyder、PyCharm等Python开发环境中,在对象或类名后面加上一个圆点“.”,稍等一秒钟则会自动列出其所有公开成员,模块也具有同样的用法。如果在圆点“.”后面再加一个下划线,则会列出该对象、类或模块的所有成员,包括私有成员。24n在Python中,以下划线开头的变量名和方法名有特殊的含义,尤其是在类的定义中。_xxx:受保护成员,不能用from module import*导入;_xxx_:系统定义的特殊成员;_xxx:私有成员,只有类对象自己能访问,子类对象不能直接访问到这个成员,但在对象外部可以通过“对象名._类名_xx
15、x”这样的特殊方式来访问。v注意:Python中不存在严格意义上的私有成员。25n 在IDLE交互模式下,一个下划线“_”表示解释器中最后一次显示的内容或最后一次语句正确执行的输出结果。3+58 8+210 _*330 _/56.0 1/0ZeroDivisionError:integer division or modulo by zero _6.026 在程序中,可以使用一个下划线来表示不关心该变量的值。for _ in range(5):print(3,end=)3 3 3 3 3 a,_=divmod(60,18)#只关心整商,不关心余数,#等价于a=60/18 a327在类中定义的方
16、法可以粗略分为四大类:公有方法、私有方法、静态方法和类方法。私有方法的名字以两个下划线“_”开始,每个对象都有自己的公有方法和私有方法,在这两类方法中可以访问属于类和对象的成员;公有方法通过对象名直接调用,私有方法不能通过对象名直接调用,只能在属于对象的方法中通过self调用或在外部通过Python支持的特殊方式来调用。如果通过类名来调用属于对象的公有方法,需要显式为该方法的self参数传递一个对象名,用来明确指定访问哪个对象的数据成员。28静态方法和类方法都可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。静态方法可以没有参数。一般将cls作为类方法的第一个参数名
17、称,但也可以使用其他的名字作为参数,并且在调用类方法时不需要为该参数传递值。29 class Root:_total=0 def _init_(self,v):#构造方法 self._value=v Root._total+=1 def show(self):#普通实例方法 print(self._value:,self._value)print(Root._total:,Root._total)classmethod#修饰器,声明类方法 def classShowTotal(cls):#类方法 print(cls._total)staticmethod#修饰器,声明静态方法 def stat
18、icShowTotal():#静态方法 print(Root._total)30 r=Root(3)r.classShowTotal()#通过对象来调用类方法1 r.staticShowTotal()#通过对象来调用静态方法1 r.show()self._value:3Root._total:1 rr=Root(5)Root.classShowTotal()#通过类名调用类方法2 Root.staticShowTotal()#通过类名调用静态方法231 Root.show()#试图通过类名直接调用实例方法,失败TypeError:unbound method show()must be cal
19、led with Root instance as first argument(got nothing instead)Root.show(r)#但是可以通过这种方法来调用方法并访问实例成员self._value:3Root._total:2 Root.show(rr)#通过类名调用实例方法时为self参数显式传递对象名self._value:5Root._total:2326.3 属性n在Python 3.x中,属性得到了较为完整的实现,支持更加全面的保护机制。336.3 属性n只读属性 class Test:def _init_(self,value):self._value=value
20、 property def value(self):#只读,无法修改和删除return self._value346.3 属性 t=Test(3)t.value3 t.value=5#只读属性不允许修改值AttributeError:cant set attribute t.v=5#动态增加新成员 t.v5 del t.v#动态删除成员 del t.value#试图删除对象属性,失败AttributeError:cant delete attribute t.value3356.3 属性n可读、可写属性 class Test:def _init_(self,value):self._value
21、=value def _get(self):return self._value def _set(self,v):self._value=v value=property(_get,_set)def show(self):print(self._value)366.3 属性 t=Test(3)t.value#允许读取属性值3 t.value=5#允许修改属性值 t.value5 t.show()#属性对应的私有变量也得到了相应的修改5 del t.value#试图删除属性,失败AttributeError:cant delete attribute376.3 属性n可读、可修改、可删除的属性
22、。class Test:def _init_(self,value):self._value=value def _get(self):return self._value def _set(self,v):self._value=v def _del(self):del self._value value=property(_get,_set,_del)def show(self):print(self._value)386.3 属性 t=Test(3)t.show()3 t.value3 t.value=5 t.show()5 t.value5396.3 属性 del t.value#删除
23、属性 t.value#对应的私有数据成员已删除AttributeError:Test object has no attribute _Test_value t.show()AttributeError:Test object has no attribute _Test_value t.value=1#为对象动态增加属性和对应的私有数据成员 t.show()1 t.value140Python类有大量的特殊方法,其中比较常见的是构造函数和析构函数,除此之外,Python还支持大量的特殊方法,运算符重载就是通过重写特殊方法实现的。Python中类的构造函数是_init_(),一般用来为数据成员
24、设置初值或进行其他必要的初始化工作,在创建对象时被自动调用和执行。如果用户没有设计构造函数,Python将提供一个默认的构造函数用来进行必要的初始化工作。Python中类的析构函数是_del_(),一般用来释放对象占用的资源,在Python删除对象和收回对象空间时被自动调用和执行。如果用户没有编写析构函数,Python将提供一个默认的析构函数进行必要的清理工作。41方法方法功能说明功能说明_new_()类的静态方法,用于确定是否要创建对象_init_()构造方法,创建对象时自动调用_del_()析构方法,释放对象时自动调用_add_()+_sub_()-_mul_()*_truediv_()/
25、_floordiv_()/_mod_()%_pow_()*_eq_()、_ne_()、_lt_()、_le_()、_gt_()、_ge_()=、!=、=_lshift_()、_rshift_()_and_()、_or_()、_invert_()、_xor_()&、|、42方法功能说明_iadd_()、_isub_()+=、-=,很多其他运算符也有与之对应的复合赋值运算符_pos_()一元运算符+,正号_neg_()一元运算符-,负号_contains_()与成员测试运算符in对应_radd_()、_rsub_反射加法、反射减法,一般与普通加法和减法具有相同的功能,但操作数的位置或顺序相反,很多
26、其他运算符也有与之对应的反射运算符_abs_()与内置函数abs()对应_bool_()与内置函数bool()对应,要求该方法必须返回True或False_bytes_()与内置函数bytes()对应_complex_()与内置函数complex()对应,要求该方法必须返回复数_dir_()与内置函数dir()对应_divmod_()与内置函数divmod()对应_float_()与内置函数float()对应,要求该该方法必须返回实数_hash_()与内置函数hash()对应_int_()与内置函数int()对应,要求该方法必须返回整数43方法方法功能说明功能说明_len_()与内置函数len
27、()对应_next_()与内置函数next()对应_reduce_()提供对reduce()函数的支持_reversed_()与内置函数reversed()对应_round_()对内置函数round()对应_str_()与内置函数str()对应,要求该方法必须返回str类型的数据_repr_()打印、转换,要求该方法必须返回str类型的数据_getitem_()按照索引获取值_setitem_()按照索引赋值_delattr_()删除对象的指定属性_getattr_()获取对象指定属性的值,对应成员访问运算符“.”方法方法功能说明功能说明_getattribute_()获取对象指定属性的值,如
28、果同时定义了该方法与_getattr_(),那么_getattr_()将不会被调用,除非在_getattribute_()中显式调用_getattr_()或者抛出AttributeError异常_setattr_()设置对象指定属性的值_base_该类的基类_class_返回对象所属的类_dict_对象所包含的属性与值的字典_subclasses_()返回该类的所有子类_call_()包含该特殊方法的类的实例可以像函数一样调用_get_()定义了这三个特殊方法中任何一个的类称作描述符(descriptor),描述符对象一般作为其他类的属性来使用,这三个方法分别在获取属性、修改属性值或删除属性时
29、被调用_set_()_delete_()例例6-1 自定义数组。在MyArray.py文件中,定义了一个数组类,重写了一部分特殊方法以支持数组之间、数组与整数之间的四则运算以及内积、大小比较、成员测试和元素访问等运算符。codeMyArray.py46 from MyArray import MyArray x=MyArray(1,2,3,4,5,6)y=MyArray(6,5,4,3,2,1)len(x)6 x+56,7,8,9,10,11 x*33,6,9,12,15,18 x.dot(y)56 x.append(7)x1,2,3,4,5,6,7 x.dot(y)The size must
30、 be equal.x9=8Index type error or out of range47 x/20.5,1.0,1.5,2.0,2.5,3.0,3.5 x/20,1,1,2,2,3,3 x%31,2,0,1,2,0,1 x23 a in xFalse 3 in xTrue x x=MyArray(1,2,3,4,5,6)x+y7,7,7,7,7,748 例例6-2 自定义支持关键字with的类。如果自定义类中实现了特殊方法_enter_()和_exit_(),那么该类的对象就可以像内置函数open()返回的文件对象一样支持with关键字来实现资源的自动管理。49class myOpen
31、:def _init_(self,fileName,mode=r):self.fp=open(fileName,mode)def _enter_(self):return self.fp def _exit_(self,exceptionType,exceptionVal,trace):self.fp.close()with myOpen(test.txt)as fp:print(fp.read()50 例例6-3 为自定义类实现关系运算符支持默认排序。from random import randrange,shuffle51class Country:#构造函数,初始化对象 def _in
32、it_(self,name,area):self._setName(name)self._setArea(area)#检查并设置国家名称 def _setName(self,name):assert isinstance(name,str),国家名称必须是字符串 self._name=name def _setArea(self,area):assert isinstance(area,int),面积必须是整数 self._area=area#返回国家名称 def getName(self):return self._name def getArea(self):return self._ar
33、ea#支持运算符 def _lt_(self,otherCountry):return self._area from mySet import Set#导入自定义集合类 x=Set(range(10)#创建集合对象 y=Set(range(8,15)z=Set(1,2,3,4,5)x0,1,2,3,4,5,6,7,8,9 y8,9,10,11,12,13,14 z.add(6)#增加元素 z1,2,3,4,5,6 z.remove(3)#删除指定元素删除成功 z1,2,4,5,656 y.pop()#随机删除一个元素11 x-y#差集0,1,2,3,4,5,6,7 x-z0,3,7,8,9
34、x.difference(y)0,1,2,3,4,5,6,7 x|y#并集0,1,2,3,4,5,6,7,8,9,10,12,13,14 x.union(y)0,1,2,3,4,5,6,7,8,9,10,12,13,14 x&z#交集1,2,4,5,657 x z#对称差集0,3,7,8,9 x.symetric_difference(y)0,1,2,3,4,5,6,7,10,12,13,14(x-y)|(y-x)0,1,2,3,4,5,6,7,10,12,13,14 x=y#测试两个集合是否相等False x y#测试集合包含关系False y xFalse x zTrue x=zTrue5
35、8 z.issubset(x)#测试z是否为x的子集True x.issuperset(z)#测试x是否为z的超集True 3 in x#测试集合中是否存在某个元素True 33 in xFalse len(y)#计算集合中元素个数6 y.clear()集合已清空 y.pop()集合已空,弹出操作被忽略59 补充例题补充例题2 自定义栈,实现基本的入栈、出栈操作。codestackDfg.py60 补充例题补充例题3 自定义队列结构,实现入队、出队操作,提供超时功能。codemyQueue.py61 补充例题补充例题4 自定义常量类。每个类和对象都有一个叫作_dict_的字典字典成员,用来记录
36、该类或对象所拥有的属性。当访问对象属性时,首先会尝试在对象属性中查找,如果找不到就到类属性中查找。Python内置类型不支持属性的增加,用户自定义类及其对象一般支持属性和自定义类及其对象一般支持属性和方法的增加与删除方法的增加与删除。在下面定义的常量类中,要求对象的成员必须大写,所有成员的值不能相同,并且不允许修改已有成员的值。6.4.2 案例精选62 class Constants:def _setattr_(self,name,value):assert name not in self._dict_,You can not modify+name assert name.isupper(
37、),Constant should be uppercase.assert value not in self._dict_.values(),Value already exists.self._dict_name=value t=Constants()t.R=3#成员不存在,允许添加 t.R=4#成员已存在,不允许修改AssertionError:You can not modify R t.G=4 t.g=4#成员必须大写AssertionError:Constant should be uppercase.t.B=4#成员的值不允许相同AssertionError:Value alre
38、ady exists.6.4.2 案例精选636.4.2 案例精选 补充例题补充例题5 超类如何知道自己被继承。class BaseClass:_total=0 def _init_subclass_(self):BaseClass._total+=1 print(f这是我被第BaseClass._total次继承!)class SubClassA(BaseClass):passclass SubClassB(BaseClass):pass646.4.2 案例精选 补充例题补充例题6 使用字典模拟稀疏矩阵。code使用字典表示和处理稀疏矩阵.py65 补充例题补充例题7 封装SQLite数据库
39、的增删改查操作。codeSQLiteDataBaseConnection.py666.4.2 案例精选 补充例题补充例题8 单链表。code单链表.py676.4.2 案例精选 补充例题补充例题9 双链表。code双链表.py686.4.2 案例精选n继承是用来实现代码复用和设计复用的机制,是面向对象程序设计的重要特性之一。设计一个新类时,如果可以继承一个已有的设计良好的类然后进行二次开发,无疑会大幅度减少开发工作量。n在继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类。派生类可以继承父类的公有成员,但是不能继承其私有成员。如果需要在派生类中调用基类的方法,可以使用内置
40、函数super()或者通过“基类名.方法名()”的方式来实现这一目的。nPython支持多继承,如果父类中有相同的方法名,而在子类中使用时没有指定父类名,则Python解释器将从左向右按顺序进行搜索。69n例例6-4 在派生类中调用基类方法。codeAccessMembersOfBaseclass.py70n 构造函数、私有方法以及普通公开方法的继承原理。class A(object):def _init_(self):#构造方法可能会被派生类继承 self._private()self.public()def _private(self):#私有方法在派生类中不能直接访问 print(_pr
41、ivate()method in A)def public(self):#公开方法在派生类中可以直接访问,也可以被覆盖 print(public()method in A)71 class B(A):#类B没有构造方法,会继承基类的构造方法 def _private(self):#这不会覆盖基类的私有方法 print(_private()method in B)def public(self):#覆盖了继承自A类的公开方法public print(public()method in B)b=B()#自动调用基类构造方法_private()method in Apublic()method in
42、 B dir(b)#基类和派生类的私有方法访问方式不一样_A_private,_B_private,_class_,.72 class C(A):def _init_(self):#显式定义构造函数 self._private()#这里调用的是类C的私有方法 self.public()def _private(self):print(_private()method in C)def public(self):print(public()method in C)c=C()#调用类C的构造方法_private()method in Cpublic()method in C dir(c)_A_pr
43、ivate,_C_private,_class_,.73 补充:补充:在Python 3.x的多继承树中,如果在中间层某类有向上一层解析的迹象,则会先把本层右侧的其他类方法解析完,然后从本层最后一个解析的类方法中直接进入上一层并继续解析,也就是在从子类到超类的反向树中按广度优先解析。如果在解析过程中,不再有向基类方向上一层解析的迹象,则同一层中右侧其他类方法不再解析。code多继承时的MRO.py7475766.6 多态原理与实现 所谓多态(polymorphism),是指基类的同一个方法在不同派生类对象中具有不同的表现和行为。派生类继承了基类行为和属性之后,还会增加某些特定的行为和属性,同时
44、还可能会对继承来的某些行为进行一定的改变,这都是多态的表现形式。Python大多数运算符可以作用于多种不同类型的操作数,并且对于不同类型的操作数往往有不同的表现,这本身就是多态,是通过特殊方法与运算符重载实现的。77 class Animal(object):#定义基类 def show(self):print(I am an animal.)class Cat(Animal):#派生类,覆盖了基类的show()方法 def show(self):print(I am a cat.)class Dog(Animal):#派生类 def show(self):print(I am a dog.)class Tiger(Animal):#派生类 def show(self):print(I am a tiger.)class Test(Animal):#派生类,没有覆盖基类的show()方法 pass6.6 多态原理与实现78 x=item()for item in(Animal,Cat,Dog,Tiger,Test)for item in x:#遍历基类和派生类对象并调用show()方法 item.show()I am an animal.I am a cat.I am a dog.I am a tiger.I am an animal.6.6 多态原理与实现79