面向对象程序设计
- Python完全采用了面向对象程序设计的思想,是真正面向对象的高级动态编程语言,完全支持面向对象的基本功能,如封装、继承、多态以及对基类方法的覆盖或重写。
- Python中对象的概念很广泛,Python中的一切内容都可以称为对象,除了数字、字符串、列表、元组、字典、集合、range对象、zip对象等等,函数也是对象,类也是对象。
- 创建类时用变量形式表示的对象属性称为数据成员,用函数形式表示的对象行为称为成员方法,成员属性和成员方法统称为类的成员。
6.1 类
6.1.1 类定义语法
- Python使用 class 关键字来定义类,class 关键字之后是一个空格,然后是类的名字,再然后是一个冒号,最后换行并定义类的内部实现。
- 类名的首字母一般要大写,当然也可以按照自己的习惯定义类名,但一般推荐参考惯例来命名,并在整个系统的设计和实现中保持风格一致,这一点对于团队合作尤其重要。
例子:
class Car:
def infor(self):
print(" This is a car ")
- 定义了类之后,可以用来实例化对象,并通过“对象名.成员”的方式来访问其中的数据成员或成员方法。
例子:
>>> car = Car()
>>> car.infor()
This is a car
- 可以使用内置方法 isinstance() 来测试一个对象是否为某个类的实例。
例子:
>>> isinstance(car, Car)
True
>>> isinstance(car, str)
False
- 关键字“pass”,类似于空语句,可以用在类和函数的定义中或者选择结构中。当暂时没有确定如何实现功能,或者为以后的软件升级预留空间,或者其他类型功能时,可以使用该关键字来“占位”。
例子:
>>> class A:
pass
>>> def demo():
pass
>>> if 5>3:
pass
6.1.2 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动态类型特点的一种重要体现。
例子:
class 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 = "Yellow" #修改实例属性
print(car2.color, Car.price, Car.name)
print(car1.color, Car.price, Car.name)
import types
def setSpeed(self, s):
self.speed = s
car1.setSpeed = types.MethodType(setSpeed, car1) #动态增加成员方法
car1.setSpeed(50) #调用成员方法
print(car1.speed)
例子:
>>> 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(self):
print(self.name+' can eat.')
>>> 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)
>>> zhang.walk()
zhang can walk.
>>> del zhang.walk #删除用户行为
>>> zhang.walk()
AttributeError: 'Person' object has no attribute 'walk'
- 函数和方法是有区别的。方法一般指与特定实例绑定的函数,通过对象调用方法时,对象本身将被作为第一个参数传递过去,普通函数并不具备这个特点。
例子:
>>> class Demo:
pass
>>> t = Demo()
>>> def test(self, v):
self.value = v
>>> t.test = test
>>> t.test #普通函数
<function test at 0x00000000034B7EA0>
>>> t.test(t, 3) #必须为self参数传值
>>> t.test = types.MethodType(test, t)
>>> t.test #绑定的方法
<bound method test of <__main__.Demo object at 0x000000000074F9E8>>
>>> 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):
self._value1 = value1
self.__value2 = value2
def show(self):
print(self._value1)
print(self.__value2)
>>> a = A()
>>> a._value1
0
>>> a._A__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
ZeroDivisionError: integer division or modulo 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
下面的代码演示了特殊成员定义和访问的方法:
>>> class Fruit:
def __init__(self):
self.__color = 'Red'
self.price = 1
>>> apple = Fruit()
>>> apple.price #显示对象公开数据成员的值
1
>>> apple.price = 2 #修改对象公开数据成员的值
>>> apple.price
2
>>> print(apple.price, apple._Fruit__color) #显示对象私有数据成员的值
2 Red
>>> apple._Fruit__color = "Blue" #修改对象私有数据成员的值
>>> print(apple.price, apple._Fruit__color)
2 Blue
>>> print(apple.__color) #不能直接访问对象的私有数据成员,出错
AttributeError: Fruit instance has no attribute '__color'
6.2 方法
- 在类中定义的方法可以粗略分为四大类:公有方法、私有方法、静态方法和类方法。
- 公有方法、私有方法都属于对象,私有方法的名字以两个下划线“__”开始,每个对象都有自己的公有方法和私有方法,在这两类方法中可以访问属于类和对象的成员;
- 公有方法通过对象名直接调用,私有方法不能通过对象名直接调用,只能在属于对象的方法中通过self调用或在外部通过Python支持的特殊方式来调用。
- 如果通过类名来调用属于对象的公有方法,需要显式为该方法的self参数传递一个对象名,用来明确指定访问哪个对象的数据成员。
- 静态方法和类方法都可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员。
- 静态方法可以没有参数。
- 一般将 cls 作为类方法的第一个参数名称,但也可以使用其他的名字作为参数,并且在调用类方法时不需要为该参数传递值。
例子:
>>> 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 staticShowTotal(): #静态方法
print(Root.__total)
>>> r = Root(3)
>>> r.classShowTotal() #通过对象来调用类方法
1
>>> r.staticShowTotal() #通过对象来调用静态方法
1
>>> r.show()
self.__value: 3
Root.__total: 1
>>> rr = Root(5)
>>> Root.classShowTotal() #通过类名调用类方法
2
>>> Root.staticShowTotal() #通过类名调用静态方法
2
>>> Root.show() #试图通过类名直接调用实例方法,失败
TypeError: unbound method show() must be called with Root instance as first argument (got nothing instead)
>>> Root.show(r) #但是可以通过这种方法来调用方法并访问实例成员
self.__value: 3
Root.__total: 2
>>> Root.show(rr) #通过类名调用实例方法时为self参数显式传递对象名
self.__value: 5
Root.__total: 2
6.3 属性
6.3.1 Python 2.x中的属性
- 在Python 2.x中,使用@property或property()来声明一个属性,然而属性并没有得到真正意义的实现,也没有提供应有的访问保护机制。在Python 2.x中,为对象增加新的数据成员时,隐藏同名的已有属性。
例子:
>>> class Test:
def __init__(self, value):
self.__value = value
@property #修饰器,用来声明属性
def value(self):
return self.__value
>>> a = Test(3)
>>> a.value
3
>>> a.value = 5 #动态添加了新成员,隐藏了定义的属性
>>> a.value
5
>>> t._Test__value #原来的私有变量没有改变
3
- 除了动态增加成员时会隐藏已有属性,下面的代码从表面看来是修改属性的值,而实际上也是增加了新成员,从而隐藏了已有属性。
例子:
>>> class Test:
def __init__(self, value):
self.__value = value
def __get(self):
return self.__value
def __set(self, v):
self.__value = v
value = property(__get, __set) #可读、可写属性
def show(self):
print self.__value
>>> t = Test(3)
>>> t.value
3
>>> t.value += 2 #动态添加了新成员
>>> t.value #这里访问的是新成员
5
>>> t.show() #访问原来定义的私有数据成员
3
>>> del t.value #这里删除的是刚才添加的新成员
>>> t.value #访问原来的属性
3
>>> del t.value #试图删除属性,失败
AttributeError: Test instance has no attribute 'value'
>>> del t._Test__value #删除私有成员
>>> t.value #访问属性,但对应的私有成员已不存在,失败
AttributeError: Test instance has no attribute '_Test__value'
下面的代码演示了普通数据成员和私有数据成员的区别:
>>> class Test:
def show(self):
print self.value
print self.__v
>>> t = Test()
>>> t.show()
AttributeError: Test instance has no attribute 'value'
>>> t.value = 3 #添加新的数据成员
>>> t.show()
3
AttributeError: Test instance has no attribute '_Test__v'
>>> t.__v = 5
>>> t.show()
3
AttributeError: Test instance has no attribute '_Test__v'
>>> t._Test__v = 5 #添加私有数据成员
>>> t.show()
3
5
6.3.2 Python 3.x中的属性
- 只读属性
>>> 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 #只读属性不允许修改值
AttributeError: can't set attribute
>>> t.v=5 #动态增加新成员
>>> t.v
5
>>> del t.v #动态删除成员
>>> del t.value #试图删除对象属性,失败
AttributeError: can't delete attribute
>>> 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
value = property(__get, __set)
def show(self):
print(self.__value)
>>> t = Test(3)
>>> t.value #允许读取属性值
3
>>> t.value = 5 #允许修改属性值
>>> t.value
5
>>> t.show() #属性对应的私有变量也得到了相应的修改
5
>>> del t.value #试图删除属性,失败
AttributeError: can't delete attribute
- 也可以将属性设置为可读、可修改、可删除。
>>> 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)
>>> t = Test(3)
>>> t.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 '_Test__value'
>>> t.show()
AttributeError: 'Test' object has no attribute '_Test__value'
>>> t.value =1 #为对象动态增加属性和对应的私有数据成员
>>> t.show()
1
>>> t.value
1
6.4 方法
6.4.1 常用特殊方法
- Python类有大量的特殊方法,其中比较常见的是构造函数和析构函数,除此之外,Python还支持大量的特殊方法,运算符重载就是通过重写特殊方法实现的。
- Python中类的构造函数是__init__(),一般用来为数据成员设置初值或进行其他必要的初始化工作,在创建对象时被自动调用和执行。如果用户没有设计构造函数,Python将提供一个默认的构造函数用来进行必要的初始化工作。
- Python中类的析构函数是__del__(),一般用来释放对象占用的资源,在Python删除对象和收回对象空间时被自动调用和执行。如果用户没有编写析构函数,Python将提供一个默认的析构函数进行必要的清理工作。
方法
6.4.2 案例精选
- 例6-1:自定义数组。在MyArray.py文件中,定义了一个数组类,重写了一部分特殊方法以支持数组之间、数组与整数之间的四则运算以及内积、大小比较、成员测试和元素访问等运算符。
>>> from MyArray import MyArray
>>> x = MyArray(1, 2, 3, 4, 5, 6)
>>> y = MyArray(6, 5, 4, 3, 2, 1)
>>> len(x)
6
>>> x + 5
[6, 7, 8, 9, 10, 11]
>>> x * 3
[3, 6, 9, 12, 15, 18]
>>> x.dot(y)
56
>>> x.append(7)
>>> x
[1, 2, 3, 4, 5, 6, 7]
>>> x.dot(y)
The size must be equal.
>>> x[9] = 8
Index type error or out of range
>>> x / 2
[0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
>>> x // 2
[0, 1, 1, 2, 2, 3, 3]
>>> x % 3
[1, 2, 0, 1, 2, 0, 1]
>>> x[2]
3
>>> 'a' in x
False
>>> 3 in x
True
>>> x < y
True
>>> x = MyArray(1, 2, 3, 4, 5, 6)
>>> x + y
[7, 7, 7, 7, 7, 7]
- 例6-3 自定义集合。
>>> from mySet import Set #导入自定义集合类
>>> x = Set(range(10)) #创建集合对象
>>> y = Set(range(8, 15))
>>> z = Set([1, 2, 3, 4, 5])
>>> x
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> y
{8, 9, 10, 11, 12, 13, 14}
>>> z.add(6) #增加元素
>>> z
{1, 2, 3, 4, 5, 6}
>>> z.remove(3) #删除指定元素
删除成功
>>> z
{1, 2, 4, 5, 6}
>>> y.pop() #随机删除一个元素
11
>>> x - y #差集
{0, 1, 2, 3, 4, 5, 6, 7}
>>> x - z
{0, 3, 7, 8, 9}
>>> 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, 6}
>>> 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 > x
False
>>> x > z
True
>>> x >= z
True
>>> z.issubset(x) #测试z是否为x的子集
True
>>> x.issuperset(z) #测试x是否为z的超集
True
>>> 3 in x #测试集合中是否存在某个元素
True
>>> 33 in x
False
>>> len(y) #计算集合中元素个数
6
>>> y.clear()
集合已清空
>>> y.pop()
集合已空,弹出操作被忽略
- 例6-4 自定义常量类。
- 每个类和对象都有一个叫作__dict__的字典成员,用来记录该类或对象所拥有的属性。当访问对象属性时,首先会尝试在对象属性中查找,如果找不到就到类属性中查找。Python内置类型不支持属性的增加,用户自定义类及其对象一般支持属性和方法的增加与删除。
>>> class Constants:
def __setattr__(self, name, value):
assert name not in self.__dict__, 'You can not modify '+name
assert name.isupper(), '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 already exists.
- 例6-5 自定义支持关键字with的类。
- 如果自定义类中实现了特殊方法__enter__()和__exit__(),那么该类的对象就可以像内置函数open()返回的文件对象一样支持with关键字来实现资源的自动管理。
class myOpen:
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())
6.5 继承机制
- 继承是为代码复用和设计复用而设计的,是面向对象程序设计的重要特性之一。设计一个新类时,如果可以继承一个已有的设计良好的类然后进行二次开发,无疑会大幅度减少开发工作量。
- 在继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类。派生类可以继承父类的公有成员,但是不能继承其私有成员。如果需要在派生类中调用基类的方法,可以使用内置函数super()或者通过“基类名.方法名()”的方式来实现这一目的。
- Python支持多继承,如果父类中有相同的方法名,而在子类中使用时没有指定父类名,则Python解释器将从左向右按顺序进行搜索。
- 例6-6:在派生类中调用基类方法。
- 构造函数、私有方法以及普通公开方法的继承原理。
>>> class A(object):
def __init__(self):
self.__private()
self.public()
def __private(self):
print('__private() method in A')
def public(self):
print('public() method in A')
>>> class B(A): #注意,类B没有定义构造函数
def __private(self):
print('__private() method in B')
def public(self):
print('public() method in B')
>>> b = B()
__private() method in A
public() method in B
>>> dir(b)
['_A__private', '_B__private', '__class__', ...]
>>> class C(A):
def __init__(self): #显式定义构造函数
self.__private()
self.public()
def __private(self):
print('__private() method in C')
def public(self):
print('public() method in C')
>>> c = C()
__private() method in C
public() method in C
>>> dir(c)
['_A__private', '_C__private', '__class__', ...]
6.6 多态原理与实现
- 所谓多态(polymorphism),是指基类的同一个方法在不同派生类对象中具有不同的表现和行为。派生类继承了基类行为和属性之后,还会增加某些特定的行为和属性,同时还可能会对继承来的某些行为进行一定的改变,这都是多态的表现形式。
- Python大多数运算符可以作用于多种不同类型的操作数,并且对于不同类型的操作数往往有不同的表现,这本身就是多态,是通过特殊方法与运算符重载实现的。
>>> 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()方法
pass
>>> 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.