第六章:面向对象程序设计
6.1面向对象程序设计
面向对象程序设计 (Object Oriented Programming, OOP)主要针对大型软件设计而提出,使得软件设计更加灵活,能够很好地支持代码复用和设计复用,并且使得代码具有更好的可读性和可扩展性。
面向对象程序设计的一条基本原则是计算机程序由多个能够起到子程序作用的单元或对象组合而成,这大大地降低了软件开发的难度,使得编程就像搭积木一样简单。
面向对象程序设计的一个关键性观念是将数据以及对数据的操作封装在一起,组成一个相互依存、不可分割的整体,即对象。对于相同类型的对象进行分类、抽象后,得出共同的特征而形成了类,面向对象程序设计的关键就是如何合理地定义和组织这些类以及类之间的关系。
Python完全采用了面向对象程序设计的思想,是真正面向对象的高级动态编程语言,完全支持面向对象的基本功能,如封装、继承、多态以及对基类方法的覆盖或重写。
Python中对象的概念很广泛,Python中的一切内容都可以称为对象,除了数字、字符串、列表、元组、字典、集合、range对象、zip对象等等,函数也是对象,类也是对象。
创建类时用变量形式表示的对象属性称为数据成员 ,用函数形式表示的对象行为称为成员方法,成员属性和成员方法统称为类的成员。
6.1.1类定义语法
Python使用class关键字来定义类,class关键字之后是一个空格,然后是类的名字,再然后是一个冒号,最后换行并定义类的内部实现。
类名的首字母一般要大写,当然也可以按照自己的习惯定义类名,但一般推荐参考惯例来命名,并在整个系统的设计和实现中保持风格一致,这一点对于团队合作尤其重要。
class Car(父类,父类,...):
def infor(self): #def 方法名字();属于实例的成员方法必须有一个参数self
print("This is a car") #方法体(函数体)
定义了类之后,可以用来实例化对象,并通过"对象名.成员"的方式来访问其中的数据成员或成员方法。
#类也属于可调用对象
>car=Car() #前面定义好了一个Car类;使用Car()类,实例化了一个car对象
>car.infor()
This is a car
在Python中,可以使用内置方法isinstance()来测试一个对象是否为某个类的实例。
>isinstance(car,Car) #isinstance的第二个参数可以是一个元组
True
>isinstance(car,str)
False
Python提供了一个关键字"pass",类似于空语句,可以用在类和函数的定义中或者选择结构中。当暂时没有确定如何实现功能,或者为以后的软件升级预留空间,或者其他类型功能时,可以使用该关键字来"占位"。
>calss A: #类
pass
>def demo():#函数
pass
>if 5>3:#选择结构
pass
6.1.2self参数
类的所有实例方法都必须至少有一个名为self的参数,并且必须是方法的第一个形参(如果有多个形参的话),self参数代表将来要创建的对象本身。
在类的实例方法中访问实例属性时需要以self为前缀(self.)。
在外部通过对象名调用对象方法时并不需要传递这个参数。
如果在外部通过类名调用对象方法则需要显式为self参数传值。
在Python中,在类中定义实例方法时将第一个参数定义为"self"只是一个习惯,而实际上类的实例方法中第一个参数的名字是可以变化的,而不必须使用"self"这个名字,尽管如此,建议编写代码时仍以self作为方法的第一个参数名字。
>class A:
def init(hahaha,v):
hahaha.value=v
def show(hahaha):
print(hahaha.value)
>a=A(3)
>a.show()
3
6.1.3类成员与实例成员
属于实例的数据成员一般是指在构造函数__init__中定义,定义和使用时必须以self作为前缀;属于类的数据成
员是在类中所有方法之外定义的。
在主程序中(或类的外部),实例属性属于实例(对象),只能通过对象名访问;而类属性属于类,可以通过类名或对象名都可以访问。
变量不需要声明,变量的类型随时改变;函数定义好,可以增加删除一个成员
在实例方法中可以调用该实例的其他方法,也可以访问类属性以及实例属性。
在Python中比较特殊的是,可以动态地为类和对象增加成员,这一点是和很多面向对象程序设计语言不同的,也是Python动态类型特点的一种重要体现。
python
#类成员与实例成员
class Car: #定义一个类
price=100000 #定义类属性
#构造函数(构造方法):属于实例的成员方法
#第一个参数是self,第二个参数是我们真正传给它的参数
def __init__(self,c):
self.color=c #定义实例属性。传进来的参数赋值给它自己数据成员
car1=Car("Red") #实例化对象。实例化car1
car2=Car("Blue")
print(car1.color,Car.price) #查看实例属性和类属性的值
Car.price=110000 #修改类属性
Car.name='QQ' #动态增加类属性
car1.color="Yellow" #修改实例属性
print(car2.color,Car.price,Car.name)
print(car1.color,Car.price,Car.name)
import types
def setSpeed(self,s):#如果是普通函数调用必须给self传值
self.speed=s
#types.MethodType,将car1转化成可识别或可认可的对象
car1.setSpeed=types.MethodType(setSpdeed,car1)#动态增加成员方法
car1.setSpeed(50)#调用成员方法。通过car1调用setSpeed
print(car1.speed)
import types
def setSpeed(self,s):#如果是普通函数调用必须给self传值
self.speed=s
#types.MethodType,将car1转化成可识别或可认可的对象
car1.setSpeed=types.MethodType(setSpeed,car1)#动态增加成员方法
car1.setSpeed(50)#调用成员方法。通过car1调用setSpeed
print(car1.speed)
Python类型的动态性使得我们可以动态为自定义类及其对象增加新的属性和行为,俗称混入(mixin)机制,这在大型项目开发中会非常方便和实用。
例如系统中的所有用户分类非常复杂,不同用户组具有不同的行为和权限,并且可能会经常改变。这时候我们可以独立地定义一些行为,然后根据需要来为不同的用户设置相应的行为能力。
>import types
>class Person(object):
def init(self,name): #构造函数(构造方法)
assert isinstance(name,str),'name must be string' #assert断言,给的参数必须是字符串,不是的话输出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(self):
print(self.name+'can eat.')
>zhang=Person('zhang')
>zhang.sing() #用户不具有该行为
AttributeError:'Person' object has no attribute 'sing'
#把sing这个函数转化为sing这个对象的方法
>zhang.sing=type.MethodType(sing,zhang) #动态增加一个新行为
>zhang.sing()
zhang can sing.
>zhang.walk()
AttributeError:'Person' object has no attribute 'walk'
>zhang.walk=types.MethodType(walk,zhang)
>zhang.walk()
zhang can walk.
>del zhang.walk #删除用户行为
>zhang.walk()
AttributeError:'Person' object has no attribute 'walk'
函数和方法的区别
在Python中,函数和方法是有区别的。方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数隐式传递过去,普通函数并不具备这个特点。
>class Demo: #测试类Demo
pass
>t=Demo() #实例化一个对象
>def test(self,v): #定义一个普通的函数
self.value=v
>t.test=test #将函数赋值给t.test
>t.test #t.test是一个普通的函数
<function test at 0x000002286D0D64D0>
>t.test(t,3) #必须为self参数传值
>t.test=types.MethodType(test,t)
>t.test #绑定的方法
<bound method test of <main.Demo object at 0x000002286D0AFA90>>
>t.test(5) #不需要为self参数传值
6.1.4私有成员与公有成员
Python并没有对私有成员提供严格的访问保护机制。
在定义类的成员时,如果成员名以两个下划线"__"或更多下划线开头而不以两个或更多下划线结束则表示是私有成员。
私有成员在类的外部不能直接访问,需要通过调用对象的公开成员方法来访问,也可以通过Python支持的特殊方式来访问。
公开成员既可以在类的内部进行访问,也可以在外部程序中使用。
>class A: #定义了一个类
def init(self,value1=0,value2=0): #定义了这个类的构造方法
self._value1=value1 #实例化对象(一个下划线,受保护的成员)
self.__value2=value2 #两个下划线,私有的成员
def setValue(self,value1,value2): #给A定义了一个成员方法setValue 普通的成员方法(公开的,可以在类的外部进行访问)
self._value1=value1
self.__value2=value2
def show(self) #show方法,输出value1和value2两个数据成员的值 普通的成员方法
print(self._value1)
print(self.__value2)
>a=A() #实例化一个对象a。构造方法在实例化对象的时候自动调用
>a._value1 #一个下划线开头的数据成员可以直接被访问
0
>a._A__value2 #两个下划线开头的私有成员,需要通过这样的特殊形式:实例化对象._所属类的名字__value2
0
在IDLE环境中,在对象或类名后面加上一个圆点".",稍等一秒钟则会自动列出其所有公开成员,模块也具有同样的用法。
如果在圆点""后面再加一个下划线,则会列出该对象、类或模块的所有成员,包括私有成员。
在Python中,以下划线开头的变量名和方法名有特殊的含义,尤其是在类的定义中。用下划线作为变量名和方法名前缀和后缀来表示类的特殊成员:
_xxx:受保护成员,不能用'from module import *'导入;
xxx: 系统定义的特殊成员;
_xxx(两个或多个下划线):私有成员,只有类对象自己能访问,子类对象不能直接访问到这个成员,但在对象外部可以通过"对象名 . 类名_xxx"这样的特殊方式来访问。
注意:Python中不存在严格意义上的私有成员。
在IDLE交互模式下,一个下划线"_"表示解释器最后一次显示的内容或最后一次语句正确执行的输出结果。
>3+5
8
>8+2
10
>_*3
30
>_/5
6.0
>1/0
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
1/0
ZeroDivisionError: division by zero
>_ #最后一次正确输出的结果
6.0
在程序中,可以使用一个下划线来表示不关心该变量的值。
>for _ in range(5): #循环五次,用_控制次数,不关心它的值
print(3,end=' ')
3 3 3 3 3
>a,=divmod(60,18) #整商,余数 等价于a,=60//18;只关心整商,不关心余数
>a
3
下面的代码演示了特殊成员定义和访问的方法:
>calss Fruit:
def init(self):
self.__color='Red' #__color(两个下划线):私有成员 通过特殊的方式访问
self.price=1 #price:公开的成员,公有成员;在类的外部直接读写
>apple=Fruit()
>apple.price #显示对象公开数据成员的值
1
>apple.price=2 #修改对象公开数据成员的值
>apple.price
2
>print(apple.price,apple._Fruit__color) #显示对象私有数据成员的值
2 Red
>apple._Fruit__color="Bule" #修改对象私有数据成员的值
>print(apple.price,appke._Fruit__color)
2 Bule
>print(apple.__color) #不能直接访问对象的私有数据成员,出错
AttributeError:Fruit instance has no attribute '__color'
6.2方法
在类中定义的方法可以粗略分为四大类:公有方法、私有方法、静态方法和类方法。
公有方法、私有方法都属于对象,私有方法的名字以两个下划线"__"开始,每个对象都有自己的公有方法和私有方法,在这两类方法中可以访问属于类和对象的成员;
公有方法通过对象名直接调用,私有方法不能通过对象名直接调用,只能在属于对象的方法中通过self调用或在外部通过Python支持的特殊方式来调用。
如果通过类名来调用属于对象的公有方法,需要显式为该方法的self参数传递一个对象名,用来明确指定访问哪个对象的数据成员。
静态方法和类方法都可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。
静态方法可以没有参数。
一般将cls 作为类方法的第一个参数名称,但也可以使用其他的名字作为参数,并且在调用类方法时不需要为该参数传递值。
有问题
python
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
@classmethod#@classmethod是修饰器(装饰器),声用来声明下面定义的是类方法
#类方法:有一个特殊的参数cls,表示类自己
def classShowTotal(cls):
#cls.__total=Root.__total
print(cls.__total)#使用cls作为前缀,访问Root类的total成员
@staticmethod#修饰器,用来说明下面的方法是静态方法
#静态方法:可以没有参数,也可以有(有参数,调用静态方法的时候,必须显式的给静态方法的每个参数传值)
def staticShowTotal():
print(Root.__total)
#类方法和静态方法,都可以毫无障碍访类的数据成员;
#而无法访问属于对象的数据成员value
r=Root(3) #使用root实例化了一个对象r
print(r.calssShowTotal()) #通过对象来调用类方法
print(r.staticShowTotal()) #通过对象调用静态方法
print(r.show())
rr=Root(5) #又实例化一个对象
print(Root.classShowTotal()) #通过类名调用类方法
print(Root.staticShowTotal()) #通过类名调用静态方法
print(Root.show())#试图通过类名直接调用实例方法,失败
print(Root.show(r))#显示传递参数,调用方法并访问实例成员
print(Root.show(rr))
6.3属性
6.3.1属性
只读属性
>class Test:#定义一个类
def init(self,value): #构造方法
self.__value=value #私有数据成员
@property#修饰器,说明下面定义的成员方法是一个属性
def value(self): #只读属性无法修改和删除
return self.__value #返回私有数据成员的值
>t=Test(3)
>t.value
3
>t.value=5 #只读属性不允许修改值
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
t.value=5
AttributeError: can't set attribute 'value'
>t.v=5 #动态增加新成员
>t.v
5
>del t.v #动态删除成员
>del t.value #试图删除对象属性,失败
Traceback (most recent call last):
File "<pyshell#13>", line 1, in <module>
del t.value
AttributeError: can't delete attribute 'value'
t.value
3
可读、可写属性
>class Test:
def init(self,value):
self.__value=value #私有数据成员
def __get(self): #私有方法 ;成员方法 (私有)
return self.__value
def __set(self,v): #私有方法
self.__value=v #功能:修改私有数据成员的值
#property:修饰器,其实就是一个函数;接收函数,改造,输出函数
value=property(__get,__set) #读取value调用__get,修改value调用__set
def show(self):
print(self.__value)
>t=Test(3) #定义了一个对象
>t.value #允许读取属性值:访问了他的值,就是调用了私有成员方法get
3
>t.value=5 #允许修改属性值
>t.value
5
>t.show() #属性对应的私有变量也得到了相应的修改;show方法输出私有成员的值
5
>del t.value #试图删除属性,失败
Traceback (most recent call last):
File "<pyshell#17>", line 1, in <module>
del t.value
AttributeError: can't delete attribute 'value'
也可以将属性设设置为可读、可修改、可删除。
>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 #内部私有的数据成员删除掉
#使用property嵌套函数(修饰器),声明一个属性value,根据动作调用不同的方法
value=property(__get,__set,__del)
def show(self):
print(self.__value)
>t=Test(3) #创建一个对象
>t.show() #调用对象的普通成员方法show
3
>t.value #读取属性的值
3
>t.value=5 #修改属性的值
>t.show()
5
>t.value
5
>del t.value #删除属性的值
>t.value #对应的私有数据成员已删除
AttributeError:'Test' object has no attribute '_Tast__value'
>t.show()
AttributeError:'Test' object has no attribute '_Tast__value'
>t.value=1 #为对象动态增加属性和对应的私有数据成员
>t.show()
1
>t.value
1
6.4特殊方法
6.4.1常用特殊方法
Python类有大量的特殊方法,其中比较常见的是构造函数和析构函数,除此之外,Python还支持大量的特殊方法,运算符重载就是通过重写特殊方法实现的。
Python中类的构造函数是__init __(),一般用来为数据成员设置初值或进行其他必要的初始化工作,在创建对象时被自动调用和执行。如果用户没有设计构造函数,Python将提供一个默认的构造函数用来进行必要的初始化工作。
Python中类的析构函数是__del __ (),一般用来释放对象占用的资源,在Python删除对象和收回对象空间时被自动调用和执行。如果用户没有编写析构函数,Python将提供一个默认的析构函数进行必要的清理工作。
>x=[3]
>x+=[3]
>x
[3, 3]
>x+[4]
[3, 3, 4]
>dir(x)
['add', 'class', 'class_getitem', 'contains', 'delattr', 'delitem', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getitem', 'gt', 'hash', 'iadd', 'imul', 'init', 'init_subclass', 'iter', 'le', 'len', 'lt', 'mul', 'ne', 'new', 'reduce', 'reduce_ex', 'repr', 'reversed', 'rmul', 'setattr', 'setitem', 'sizeof', 'str', 'subclasshook', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>dir({}) #创建空字典
['class', 'class_getitem', 'contains', 'delattr', 'delitem', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'getitem', 'gt', 'hash', 'init', 'init_subclass', 'ior', 'iter', 'le', 'len', 'lt', 'ne', 'new', 'or', 'reduce', 'reduce_ex', 'repr', 'reversed', 'ror', 'setattr', 'setitem', 'sizeof', 'str', 'subclasshook', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
>dir(set()) #创建空集合
['and', 'class', 'class_getitem', 'contains', 'delattr', 'dir', 'doc', 'eq', 'format', 'ge', 'getattribute', 'gt', 'hash', 'iand', 'init', 'init_subclass', 'ior', 'isub', 'iter', 'ixor', 'le', 'len', 'lt', 'ne', 'new', 'or', 'rand', 'reduce', 'reduce_ex', 'repr', 'ror', 'rsub', 'rxor', 'setattr', 'sizeof', 'str', 'sub', 'subclasshook', 'xor', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
>(3)
['abs', 'add', 'and', 'bool', 'ceil', 'class', 'delattr', 'dir', 'divmod', 'doc', 'eq', 'float', 'floor', 'floordiv', 'format', 'ge', 'getattribute', 'getnewargs', 'gt', 'hash', 'index', 'init', 'init_subclass', 'int', 'invert', 'le', 'lshift', 'lt', 'mod', 'mul', 'ne', 'neg', 'new', 'or', 'pos', 'pow', 'radd', 'rand', 'rdivmod', 'reduce', 'reduce_ex', 'repr', 'rfloordiv', 'rlshift', 'rmod', 'rmul', 'ror', 'round', 'rpow', 'rrshift', 'rshift', 'rsub', 'rtruediv', 'rxor', 'setattr', 'sizeof', 'str', 'sub', 'subclasshook', 'truediv', 'trunc', 'xor', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
6.4.2案例精选
例6-1
:自定义数组。在MyArray.py文件中,定义了一个数组类,重写了一部分特殊方法以支持数组之间、数组与整数之间的四则运算以及内积、大小比较、成员测试和元素访问等运算符。
使用列表封装,实现功能
p51