Python 面向对象编程最大的乐趣莫过于特殊方法的重写,使得自定义的对象,可以像 Python 内建对象一样,进行函数操作。
一. 重写特殊方法 overwrite
在自定义的类内添加相应的特殊方法,让自定义类创建的实例,可以像内建对象一样进行函数操作。
注:特殊方法又称为双下方法、魔术方法,以双下划线开头,并以双下划线结尾。
1.1 对象转字符串的重写方法
对象转字符串常用的函数有2个:repr
和 str
。
-
repr(x)
返回一个能代表此对象的表达式字符串,通常eval(repr(obj)) = obj
; -
str(obj)
通过给定对象返回一个字符串,与repr
相反,str(obj)
返回的字符串通常是给人阅读的。
注:
eval(str)
将字符串当成表达式来运行。
示例 1:
>> S = "python"
>> str(S)
'python'
>> repr(S)
"'python'"
>> L = [1,2,3]
>> str(L)
'[1, 2, 3]'
>> repr(L)
'[1, 2, 3]'
对于字符串而言,str
和 repr
调用返回的结果是不同的,str
强调对人的友好性,我们可以很方便地了解到该对象描述的内容;repr
返回的结果多了一对单引号,对机器友好,通过将此字符串中的内容当作程序来运行,即可创建一个字符串。
对于列表而言,二者返回的则是同一个结果,'[1, 2, 3]'
这种形式即易于人的阅读,我们创建列表也是这样 [1,2,3]
创建的。
repr(o)
实际上会调用 o.__repr__
方法,而 str(o)
实际上会调用 o.__str__
方法;因此我们可以重写上述方法,并返回字符串,即可定制 repr
和 str
的返回内容。
class MyNumber:
def __init__(self, value):
self.data = value
def __str__(self):
return "自定义数据类型 MyNumber(%d)" % self.data
def __repr__(self):
return 'MyNumber(%d)' % self.data
运行结果:
>> MyNumber(100).__str__()
'自定义数据类型 MyNumber(100)'
>> str(MyNumber(100))
'自定义数据类型 MyNumber(100)'
>> repr(MyNumber(200))
'MyNumber(200)'
>> MyNumber(200).__repr__()
'MyNumber(200)'
>> num = eval(repr(MyNumber(300)))
>> num
MyNumber(300)
>> num.data
300
注意:调用
str(o)
首先查找o.__str__
调用并返回结果,o.__str__
不存在,则调用obj.__repr__()
方法并返回结果。
如果o.__repr__
方法依然不存在,则调用object
类的__repr__
实例方法,返回结果类似:<__main__.MyNumber object at xxx>
。
1.2 内建函数重写
下面是几个常用的 Python 内建函数及其对应的特殊方法:
重写方法 | 调用 |
---|---|
__abs__ |
abs(obj) |
__len__ |
len(obj) |
__reversed__ |
reversed(obj) |
__round__ |
round(obj) |
示例:
class MyInteger:
def __init__(self, v):
self.data = v
def __repr__(self):
return 'MyInteger(%d)' % self.data
def __str__(self):
return '%d' % self.data
def __abs__(self):
"""此方法用于制定 abs(obj) 函数取值时返回的结果"""
if self.data < 0:
return MyInteger(-self.data)
return MyInteger(self.data)
def __len__(self):
return len(self.__abs__().__str__())
def __reversed__(self):
return MyInteger(self.data * -1)
运行结果:
>> num = MyInteger(-100)
>> print("自定义的整数: {}".format(num))
自定义的整数: -100
>> print("{} 的绝对值为 {}".format(repr(num), abs(num)))
MyInteger(-100) 的绝对值为 100
>> print("{} 的长度为 {}".format(repr(num), len(num)))
MyInteger(-100) 的长度为 3
>> print("{} 反转后为 {}".format(repr(num), repr(reversed(num))))
MyInteger(-100) 反转后为 MyInteger(100)
1.3 数值转换函数重写
下面是几个常用的 Python 数值转换函数及其对应的特殊方法:
重写方法 | 函数调用 |
---|---|
__complex__ |
complex(obj) |
__int__ |
int(obj) |
__float__ |
float(obj) |
__bool__ |
bool(obj) |
示例:
class MyList:
def __init__(self, iterable=None):
if iterable is None:
raise ValueError('iterable cat not be None')
self.data = [x for x in iterable]
def __repr__(self):
print('__repr__ 被调用')
return 'MyList(iterable={})'.format(self.data)
运行结果:
>> L1 = MyList(iterable=[1,2,3,4])
>> print("my list L1: {}, {}".format(L1, bool(L1)))
__repr__ 被调用
my list L1: MyList(iterable=[1, 2, 3, 4]), True
>> L2 = MyList(iterable=[0,0,0,0])
>> print("my list L2: {}, {}".format(L2, bool(L2)))
__repr__ 被调用
my list L2: MyList(iterable=[0, 0, 0, 0]), True
由于自定义的 MyList
没有实现 __bool__
方法,因此调用 bool(L)
实则调用的是父类 object
实现的 __bool__
方法。
如果在自定义类中实现了 __len__
,则 bool(L)
将优先调用该方法获取返回结果:
class MyList:
def __init__(self, iterable=None):
if iterable is None:
raise ValueError('iterable cat not be None')
self.data = [x for x in iterable]
def __repr__(self):
print('__repr__ 被调用')
return 'MyList(iterable={})'.format(self.data)
def __len__(self):
print('__len__ 被调用')
return len(self.data)
运行结果:
>> L1 = MyList(iterable=[1,2,3,4])
>> print("my list L1: {}, {}".format(L1, bool(L1)))
__len__ 被调用
__repr__ 被调用
my list L1: MyList(iterable=[1, 2, 3, 4]), True
>> L2 = MyList(iterable=[0,0,0,0])
>> print("my list L2: {}, {}".format(L2, bool(L2)))
__len__ 被调用
__repr__ 被调用
my list L2: MyList(iterable=[0, 0, 0, 0]), True
下面我们重写 __bool__
方法:
class MyList:
def __init__(self, iterable=None):
if iterable is None:
raise ValueError('iterable cat not be None')
self.data = [x for x in iterable]
def __repr__(self):
print('__repr__ 被调用')
return 'MyList(iterable={})'.format(self.data)
def __len__(self):
print('__len__ 被调用')
return len(self.data)
def __bool__(self):
print("__bool__ 方法被调用")
if len(self.data) == 0:
return False
for x in self.data:
if x:
return True
return False
运行结果:
>> L1 = MyList(iterable=[1,2,3,4])
>> print("my list L1: {}, {}".format(L1, bool(L1)))
__bool__ 方法被调用
__repr__ 被调用
my list L1: MyList(iterable=[1, 2, 3, 4]), True
>> L2 = MyList(iterable=[0,0,0,0])
>> print("my list L2: {}, {}".format(L2, bool(L2)))
__bool__ 方法被调用
__repr__ 被调用
my list L2: MyList(iterable=[0, 0, 0, 0]), False
二. 自定义类实现迭代器
2.1 迭代器
迭代器即可以通过 next
函数获取值的对象。
迭代器协议:对象能够使用
next
函数获取下一项数据,在没有下一项数据时触发Stopiteration
异常来终止迭代的约定。
示例:reversed
函数返回的即是一个迭代器,迭代器是有状态的,只能遍历一次
>> my_iter = reversed([1,2,3])
>> next(my_iter)
3
>> next(my_iter)
2
>> next(my_iter)
1
>> next(my_iter)
...
StopIteration:
查看 my_iter
迭代器的帮助信息,我们可以看到 list_reverseiterator
实现 __next__
特殊方法的同时,还实现了 __iter__
方法:
实现了 __next__
,说明实例可以使用 next
函数获取下一项,该实例是一个迭代器。同理实现了 __iter__
方法,说明实例可以使用 iter
函数获取一个迭代器。
>> my_iter = reversed([1,2,3])
>> iter(my_iter)
<list_reverseiterator at 0x1e99f7c61c0>
>> iter(my_iter)
<list_reverseiterator at 0x1e99f7c61c0>
>> my_iter
<list_reverseiterator at 0x1e99f7c61c0>
上述结果中,我们两次调用 iter(my_iter)
返回的都是同一个对象,并且都是迭代器 my_iter
本身。这里补充一下,如果传入的是容器对象,则每次都将返回新的迭代器:
>>L = list(range(5))
>> L
[0, 1, 2, 3, 4]
>> iter(L)
<list_iterator at 0x1e9a1bf5d00>
>> iter(L)
<list_iterator at 0x1e9a1c0dcd0>
这也是为什么,在容器上我们可以重复遍历,而迭代器只能遍历一次。
2.2 可迭代对象
可迭代对象是指能用 iter(obj)
返回迭代器的对象。
list
是一个可迭代类型,因此可以在列表上调用 iter
函数,返回生成器:
>> L = list(range(3))
>> L = iter(L)
>> next(L)
0
>> next(L)
1
>> next(L)
2
>> next(L)
...
StopIteration:
可迭代对象内部要实现 __iter__
方法来返回迭代器,__iter__
方法语法形式如下:
class MyIterable:
def __iter__(self):
语句块
return 迭代器
注:
__iter__
方法必须返回一个迭代器,因此,我们也可以将__iter__
方法实现为生成器,来构建自己的容器类。详情可参考:https://www.jianshu.com/p/0bae644d393a 。
示例:下面是一个综合的案例演示
class MyIterator:
"""用自定义的类MyIterator实现迭代器"""
def __init__(self, start, stop, step):
# 用来记录迭代器的起始位置和当前位置
self.start = start
self.stop = stop
self.step = step
def __next__(self):
"""实现迭代器协议"""
if self.start >= self.stop:
raise StopIteration
# 将要返回的数据存入一个变量
r = self.start
self.start += self.step # 迭代器后移
return r
class MyRange:
"""使用自定义的类MyRange实现可迭代对象"""
def __init__(self, start, stop=None, step=1):
if stop is None:
stop = start
start = 0
self.start = start
self.stop = stop
self.step = step
def __repr__(self):
return 'MyRange(%d,%d,%d)' % (self.start, self.stop, self.step)
def __iter__(self):
"""用于将MyRange类型创建的对象当做可迭代对象"""
# 返回值必须为一个迭代器
return MyIterator(self.start, self.stop, self.step)
[ 运行结果 ] 使用自定义的可迭代对象类 MyRange
构造列表:
>> L = [x for x in MyRange(5)]
>> L
[0, 1, 2, 3, 4]
[ 运行结果 ] 创建可迭代对象,并返回生成器:
>> R = iter(MyRange(1, 6, 2))
>> next(R)
1
>> next(R)
3
>> next(R)
5
>> next(R)
...
StopIteration: