第6章 面向对象程序设计
6.1 类的定义与使用
6.1.1 类定义语法
- 创建一个类
class Car: # 新式类必须有至少一个基类
def infor(self):
print("This is a car")
- 实例化对象
car = Car()
car.infor() # 通过对象名调用方法。
# This is a car
-内置方法isinstance()
:测试一个对象是否为某个类的实例。
isinstance(car, Car)
isinstance(car, str)
# True
# False
- 关键字 pass:类似空语句,提供占位功能。
class A:
pass
def demo():
pass
if 5 > 3:
pass
6.1.2 self参数
- 类的所有实例方法都必须至少有一个"self"的参数
- "self"参数必须是方法的第一个形参
- "self"参数代表将要创建的对象本身
- 在类的实例方法中访问实例属性是需要以self为前缀
- 在类外部用对象名调用对象方法时并不需要传递self参数
- 在类外部通过类名调用对象方法需要显式为self参数传值
- "self"参数名字是可以变化的
class A:
def __init__(lalala, v):
lalala.value = v
def show(lalala):
print(lalala.value)
a = A(3)
a.show()
# 3
要注意this指针和self指针的作用,指向的是实例自己
6.1.3 类成员与实例成员
- 类成员:指数据成员,或者广义上的属性
- 属性有两种:一种是实例属性;另一种是类属性
- 实例属性
- 一般是指在构造函数__init__()中定义的,使用时必须以self作为前缀
- 实例属性属于实例(对象),只能通过对象名访问
- 类属性
- 在类中所有方法之外定义的数据成员
- 类属性属于类,可以通过类名或对象名访问
- 实例属性
- 类的方法中可调用类本身的其他方法,可访问类和类对象属性
- Python中可以动态地为类和对象增加成员
import types
class Car:
price = 100000 # 定义类属性
def __init__(self, c):
self.color = c # 定义实例属性
car1 = Car('Red')
car2 = Car('Blue')
print(car1.color, Car.price)
# Red 100000 创建实例成功
Car.price = 110000 # 修改类属性,通过类名访问
Car.name = "QQ" # 增加类属性
car1.color = "Yellow" # 修改实例属性,通过对象名访问
print(car2.color, Car.price, Car.name)
# Blue 110000 QQ 可以通过对象名.属性名/类名.属性名的形式访问数据成员
print(car1.color, Car.price, Car.name)
# Yellow 110000 QQ
print(car1.color, car1.price, car1.name)
# Yellow 110000 QQ
- Python中可以动态地为类和对象增加成员
def setSpeed(self, s):
self.speed = s
car1.setSpeed = types.MethodType(setSpeed, Car) # 动态为对象增加成员方法
car1.setSpeed(50)
print(car1.speed)
# 50 可以动态的为对象增加成员方法要使用types的MethodType方法
- 只有MethodType才能为类添加类的方法,否则只是显式的调用函数本身。
def addMethod():
car2.setSpeed = setSpeed # 把setSpeed函数赋给car2的一个成员变量
print(car2.setSpeed) # 此时setSpeed只是在内存中的一个函数
# <function setSpeed at 0x0000019EE6724700>
car2.setSpeed(car2, 50) # 调用setSpeed(self,s)函数需要传入self指针和s数值
print(car2.speed)
# 50
car2.setSpeed = types.MethodType(setSpeed, Car) # 动态为对象增加成员方法
print(car2.setSpeed) # 此时setSpeed函数是类内部的一个方法
# <bound method setSpeed of <class '__main__.Car'>>
car2.setSpeed(50) # 通过对象名设置数据成员的数值
print(car2.speed)
# 50
6.1.4 私有成员与公有成员
-
Python没有对私有成员提供严格的访问保护机制。
- 在定义类的属性时,如果属性名以两个下划线"__"开头则表示是私有属性。
- 私有属性在类的外部不能直接访问,需要通过调用对象的公有成员方法来访问,或者通过Python支持的特殊方法来访问。
- 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()
print(a._value1) # _value1只是保护成员
print(a._A__value2) # __value2是私有成员,不能直接访问。在外部访问对象的私有成员
# 特殊方法:对象名._类名__私有成员名
- Python 中,以下划线开头的变量名和方法名有特殊的含义,尤其是在类的定义中。用下划线作为变量名和方法名前缀和后缀表示类的特殊成员。
- _xxx: 这样的对象叫做保护成员,不能用"from module import *"导入,只有类对象和子类对象能访问这些成员。
- xxx: 系统定义的特殊成员。
- __xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问到这个成员,但在对象外部可以通过“对象名._类名__私有成员名”这样的特殊方式类访问。
- Python中不存在严格意义上的私有成员。
- 在IDLE交互模式下,一个下划线’_'表示解释器中最后一次显示的内容或最后一次语句正确执行的输出结果。
>>> 3 + 5
8
>>> _ + 2
10
>>> _ * 3
30
>>> _ / 5
6.0
>>> 1 / 0
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
1 / 0
ZeroDivisionError: division by zero
>>> _
6.0
- 特殊成员定义和访问的方法
class Fruit:
def __init__(self):
self.__color = 'Red'
self.price = 1
apple = Fruit()
print(apple.price) # 显示对象公开数据成员的值
# 1
apple.price = 2 # 修改对象公开数据成员的值
print(apple.price)
# 2 apple的公开数据成员被修改
print(apple.price, apple._Fruit__color) # 显示对象私有数据成员的值
# 2 Red 显示apple的私有数据成员
apple._Fruit__color = 'Blue' # 修改对象私有数据成员的值
print(apple.price, apple._Fruit__color)
# 2 Blue 私有成员被修改
print(apple.__color)
# AttributeError: 'Fruit' object has no attribute '__color'
# 私有成员不能直接被访问
peach = Fruit()
print(peach.price,peach._Fruit__color)
# 1 Red 对于对象私有成员和共有成员的修改只是针对该对象,而不影响类的私有/共有成员函数
6.2 方法
类中定义的方法可以粗略分为四大类:
-
公有方法
-
私有方法
-
静态方法
-
类方法
-
公有方法
- 属于对象,可以访问属于类和对象的成员,公有方法通过对象名直接调用,通过类名来调用属于对象的公有方法,需要显式为该方法"self"参数传递一个对象名,用来确定访问哪个对象的数据成员。
-
私有方法
- 属于对象,私有方法的名字以两个下划线“__”开始,可以访问属于类和对象的成员,私有方法在属于对象的方法中通过“self”调用或在外部通过Python支持的特殊方法来调用
-
静态方法
- 通过类名和对象名调用,不能直接访问属于对象的成员,只能访问属于类的成员
-
类方法
- 通过类名和对象名调用,不能直接访问属于对象的成员,只能访问属于类的成员,一般将“cls”作为类方法的第一个参数名称,但也可以使用其他的名字作为参数,并且在调用类方法时不需要为“cls”参数传递值
-
调用:
- 对象名.公有方法; 对象名.类方法;对象名.静态方法
- 类名.公有方法(对象名);类名.类方法;类名.静态方法
- 私有方法的调用
- 对象方法中self.init(self,v)
- 外部特殊方法
class Root:
__total = 0 # 定义类的私有数据成员
def __init__(self, v): # 定义私有方法(构造函数)
self.__value = v
Root.__total += 1
def show(self): # 定义共有方法,self传递一个对象名,确定操作对象
print('self.__value:', self.__value)
print('Root.__total:', Root.__total)
@classmethod # 类方法的标记
def classShowTotal(cls): # 类方法,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: show() missing 1 required positional argument: 'self'
Root.show(r) # 可以通过这种方法来调用方法并访问实例成员
# self.__value: 3
# Root.__total: 2
r.show()
# self.__value: 3
# Root.__total: 2
Root.show(rr) # 通过类名调用实例方法时为self参数显示传递对象名
# self.__value: 5
# Root.__total: 2
rr.show()
# self.__value: 5
# Root.__total: 2
6.3 属性
Python 2.x和Python3.x对属性的实现和处理方式不一样,内部实现有较大的差异,使用时应注意二者之间的区别。
属性的作用
# 廖雪峰 @property的使用 补充
class Student(object):
def __init__(self, name):
self._name = name
'''属性的作用:我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。'''
# 设置name的get属性
@property
def name(self):
return self._name
@name.setter
def name(self, name):
if isinstance(name, str):
self._name = name
else:
print('type error!!!')
def show(self):
print(self._name)
s = Student('wendy')
s.show()
s.name = 'lily' # 相当于s._name = 'lily' ,但是调用的name的set函数;增加了对于输入值的判断
print(s.name) # 相当于s._name, 调用的name的get函数。
s.name = 1 # 通过name的set函数判断输入类型不合理,所以没有进行对象成员的修改。
print(s.name)
print(dir(s))
6.3.1 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)
print(a. value)
# 3
a.value = 5 # 动态添加了新成员,隐藏了定义的属性
# 并不是对于Test类的私有变量value的修改,而是添加了一个同名的变量value,并且赋值为5
print(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) # 表示对于私有成员value赋予get和set的权限
def show(self):
print self.__value
t = Test(3)
print(t.value)
# 3
t.value+=2 # 动态添加新成员
print(t.value) # 这里访问的是新成员
# 5
t.show() # 访问原来定义的私有数据成员
# 3
del t.value # 这里删除的是刚才添加的新成员
print(t.value) # 访问原来的属性
# 3
del t.value # 试图删除属性
AttributeError: Test instance has no attribute 'value'
- Python 2.x 中私有成员和普通成员之间的关系
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 # 通过对象增加新成员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)
print(t.value)
# 3
t.value = 5 # 只读属性不允许修改值
# AttributeError: can't set attribute
t.v = 5 # 动态增加新成员
print(t.v)
del t.v # 动态删除成员
print(t.v) # 成功删除成员
# AttributeError: 'Test' object has no attribute 'v'
del t.value # 试图删除对象属性,失败
# AttributeError: can't delete attribute
print(t.value) # 显示属性值
# 3
- 把属性设置为可读,可修改,而不允许删除。
value = property(__get, __set) # 通过property()分配权限
''' 把属性设置为可读,可修改,而不允许删除。'''
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) # 通过property()分配权限
def show(self):
print(self.__value)
t = Test(3)
print(t.value) # 允许读取属性值
# 3
t.value = 5 # 允许修改属性值
print(t.value)
# 5
t.show() # 属性对应的私有变量也得到了相应的修改
# 5
'''del t.value # 试图删除属性,失败
# AttributeError: can't delete attribute'''
- 把属性设置为可读,可修改,可删除。
value = property(__get, __set, __del) # 通过property()分配权限
''' 把属性设置为可读,可修改,可删除。'''
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 = 5
t.show()
# 5
del t.value
''' # 删除了对象的属性
print(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
print(t.value)
# 1
6.4 特殊方法与运算符重载
6.4.1 常用特殊方法
- Python中类的构造函数是__init__()
- 一般用来为数据成员设置初值或进行其他必要的初始化工作。
- 在创建对象时被自动调用和执行,可以通过为构造函数定义默认值参数类实现类似于其他语言中构造函数重载的目的。
- 如果用户没有涉及构造函数,Python将提供一个默认的构造函数用来进行必要的初始化工作。
- Python中类的析构函数是__del__()
- 一般用来释放对象占用的资源。
- 在Python删除对象和收回对象空间时被自动调用和执行。
- 如果用户没有编写析构函数,Python将提供一个默认的析构函数进行必要的清理工作。
- 其他方法
6.4.2 案例精选
- 补充
- 在Python中,可以同一行显示多条语句,只需用分号“;”隔开即可。
- 在Python中,可以使用反斜杠()将一行语句分为多行解释。但是语句包含的{}、[]、()中的内容不需要使用多行连接符。
"""6-1 自定义一个数组类,支持数组与数字之间的四则运算数组之间的加法运算、内积运算和大小比较,数组元素访问和修改,以及成员测试等功能"""
class MyArray:
"""All the elements in this array must be numbers"""
__value = [] # 类的私有对象
__size = 0
def __IsNumber(self, n):
"""
在Python中,可以使用反斜杠(\)将一行语句分为多行解释。但是语句包含的{}、[]、()中的内容不需要使用多行连接符。
int:整型; float:浮点数; complex:复数
"""
if (not isinstance(n, int)) and \
(not isinstance(n, float)) and \
(not isinstance(n, complex)):
return False
return True
def __init__(self, *args):
if not args:
self.__value = []
else:
for arg in args:
if not self.__IsNumber(arg):
print('All elemtents must be numbers')
return
self.__value = list(args)
def __add__(self, n): # 数组中每个元素都与数字n相加,或两个数组相加
if self.__IsNumber(n): # 若传入的参数n是数字
b = MyArray()
for v in self.__value:
b.__value.append(v + n)
return b
elif isinstance(n, MyArray): # 若传入的参数n是数组
if len(n.__value) == len(self.__value):
c = MyArray()
for i, j in zip(self.__value, n.__value):
c.__value.append(i + j)
return c
else:
print('Length not equal')
else:
print('Not supported')
def __sub__(self, n): # 数组中每个元素都与数字n相减,返回新数组
if not self.__IsNumber(n):
print('-operating with', type(n), 'and number type is not supported.')
return
b = MyArray()
for v in self.__value:
b.__value.append(v - n)
return b
def __mul__(self, n): # 数组中每个元素都与数字n相乘,返回新数组
if not self.__IsNumber(n):
print('* operating with', type(n), 'and number type is not supported.')
return
b = MyArray()
for v in self.__value:
b.__value.append(v * n)
return b
def __truediv__(self, n): # 数组中每个元素都与数字n相乘,返回新数组
if not self.__IsNumber(n):
print(r'/ operating with', type(n), 'and number type is not supported.')
return
b = MyArray()
for v in self.__value:
b.__value.append(v / n)
return b
def __floordiv__(self, n): # 数组中每个元素都与数字n整除,返回新数组
if not isinstance(n, int):
print(n, ' is not an integer')
return
b = MyArray()
for v in self.__value:
b.__value.append(v // n)
return b
def __mod__(self, n): # 数组中每个元素都与数字n求余数,返回新数组
if not self.__IsNumber(n):
print(r'% operating with', type(n), 'and number type is not supported.')
return
b = MyArray()
for v in self.__value:
b.__value.append(v % n)
return b
def __pow__(self, n): # 数组中每个元素都与数字n进行幂计算,返回新数组
if not self.__IsNumber(n):
print('** operating with', type(n), 'and number type is not supported.')
return
b = MyArray()
for v in self.__value:
b.__value.append(v ** n)
return b
def __len__(self):
return len(self.__value)
def __repr__(self): # 直接使用对象作为语句时调用该函数
# equibalent to return'self.__value)
return repr(self.__value)
def __str__(self): # 使用print()函数输出对象时调用该函数
return str(self.__value)
def append(self, v): # 追加元素
if not self.__IsNumber(v):
print('Only number can be appended.')
return
self.__value.append(v)
def __getitem__(self, index): # 获取指定位置的元素值
if self.__IsNumber(index) and 0 <= index < len(self.__value):
return self.__value[index]
else:
print('Index out of range.')
def __setitem__(self, index, v): # 设置指定位置的元素值
if not self.__IsNumber(v):
print(v, 'is not a number')
elif (not isinstance(index, int) or index < 0 or index >= len(self.__value)):
print('Index type error or out of range.')
else:
self.__value = v
def __contains__(self, v): # 成员测试运算符in
if v in self.__value:
return True
return False
def dot(self, v): # 模拟向量内积
if not isinstance(v, MyArray):
print(v, 'must be an instance of MyArray.')
return
if len(v) != len(self.__value):
print('The size must be equal.')
return
b = MyArray()
for m, n in zip(v.__value, self.__value):
b.__value.append(m * n)
return sum(b.__value)
def __eq__(self, v): # 关系运算符==
if not isinstance(v, MyArray):
print(v, 'must be an instance of MyArray.')
return False
if self.__value == v.__value:
return True
return False
def __lt__(self, v): # 关系运算符<
if not isinstance(v, MyArray):
print(v, 'must be an instance of MyArray.')
return False
if self.__value < v.__value:
return True
return False
if __name__ == '__main__':
print('Please use me as a module.')
测试代码:
from MyArray import MyArray
x = MyArray(1, 2, 3, 4, 5, 6)
y = MyArray(6, 5, 4, 3, 2, 1)
print(len(x))
# 6
print(x + 5)
# [6, 7, 8, 9, 10, 11]
print(x * 3)
# [3, 6, 9, 12, 15, 18]
print(x.dot(y))
# 56
x.append(7)
print(x)
# [1, 2, 3, 4, 5, 6, 7]
print(x.dot(y))
# The size must be equal.
'''
x[9] = 8
# Index type error or out of range.
'''
print(x / 2)
# [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
print(x // 2)
# [0, 1, 1, 2, 2, 3, 3]
print(x % 3)
# [1, 2, 0, 1, 2, 0, 1]
print(x[2])
# 3
print('a' in x)
# False
print(x < y)
# True
x = MyArray(1, 2, 3, 4, 5, 6)
print(x + y)
# [7, 7, 7, 7, 7, 7]
6.5 继承机制
- 继承的目的是代码复用和设计复用
- 继承关系中,已有的、设计好的类称为父类或基类,新设计的类称为子类或派生类。
- 派生类可以继承父类的公有成员,但是不能继承其私有成员。
- 如果需要在派生类中调用基类的方法,可以使用内置函数super(),或者通过“基类名.方法()”的方式来实现。
# 调用基类构造方法初始化基类的私有成员,注意使用格式
# super(Teacher,self)返回的是基类的指针,推荐使用super()方法调用基类构造。
super(Teacher, self).__init__(name, age, sex)
- Python支持多继承,如果父类中有相同的方法名,而在子类中使用是没有指定父类名,则Python解释器将从左向右按顺序搜索。
案例
''' 6.2 在派生类中调用基类方法
首先设计Person类,然后以Person为基类派生Teacher类,分别创建Person类和Teacher类的对象,
并在派生类对象中调用基类方法。'''
class Person:
def __init__(self, name='', age=20, sex='man'):
self.setName(name)
self.setAge(age)
self.setSex(sex)
def setName(self, name):
if not isinstance(name, str):
print('name must be string.')
return
self.__name = name
def setAge(self, age):
if not isinstance(age, int):
print('name must be integer.')
return
self.__age = age
def setSex(self, sex):
if sex != 'man' and sex != 'woman':
print('sex must be "man" or "woman"')
return
self.__sex = sex
def show(self):
print('Name', self.__name)
print('Age', self.__age)
print('Sex', self.__sex)
class Teacher(Person): # 派生类
def __init__(self, name="", age=30, sex='man', department='Computer'):
super(Teacher, self).__init__(name, age, sex)
# #or, use another method like below:
# Person.__init__(self, name, age, sex)
self.setDepartment(department)
def setDepartment(self, department):
if not isinstance(department, str):
print('department must be a string.')
return
self.__department = department
def show(self):
# Person.show()
super(Teacher, self).show()
print('Department:', self.__department)
if __name__ == '__main__':
zhangsan = Person('Zhang San', 19, 'man')
zhangsan.show()
lisi = Teacher('Li Si', 32, 'man', 'Math')
lisi.show()
lisi.setAge(40) # 派生类调用基类的方法
lisi.show()
更好的理解Python类的继承机制
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 # 调用了A类的私有函数的构造函数,不能访问派生类B的私有函数
# public() method in B # 调用了A类的共有成员的构造函数
print(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 # 调用了C类的构造方法,由于被覆写
# public() method in C
print(dir(c))
# ['_A__private', '_C__private', '__class__', ...]
- 课外思考
class D:
attr = 3
class B(D):
pass
class E:
attr = 2
class C(E):
attr = 1
class A(B,C):
pass
X = A()
print(X.attr)
# 3
class D:
attr = 3
class B(D):
pass
class E:
attr = 2
class C(E):
attr = 1
class A(B,C):
pass
X = A()
print(X.attr)
# 1
- 类组合的使用
- 使用super()方法的作:
- 如果对于基类进行增删,使用super()时,不需要对派生类进行修改。
- 如果只是再派生类自己定义,则对于基类修改的时候,需要对派生类进行手动修改。