Python进阶与拾遗4:Python中的运算符重载
- 运算符重载的相关概念
- 常见的运算符重载方法
- 运算符重载的常见例子
- 索引和分片:\_\_getitem\_\_和\_\_setitem\_\_
- 迭代器对象:\_\_iter\_\_和\_\_next\_\_
- 普通迭代器的对象
- 有多个迭代器的对象
- 成员关系:\_\_contains\_\_, \_\_iter\_\_和\_\_getitem\_\_
- 属性引用:\_\_getattr\_\_和\_\_setattr\_\_
- 返回字符串表达式:\_\_str\_\_和\_\_repr\_\_
- 加法,右侧加法和原处加法:\_\_add\_\_, \_\_radd\_\_和\_\_iadd\_\_
- 比较:\_\_lt\_\_,\_\_gt\_\_与其他方法
- 布尔测试:\_\_bool\_\_和\_\_len\_\_
- 对象析构函数:\_\_del\_\_
- Call表达式:\_\_call\_\_
在Python这一门
面向对象的编程语言中,
运算符重载是类程序中常见的技巧与方法。本篇博文总结Python中的运算符重载相关知识,下面开始干货。
运算符重载的相关概念
运算符重载意味着在类方法中拦截内置的操作,这样当类的实例出现在内置操作中,Python会自动调用你的方法,并且方法的返回值变成了相应的操作结果。
- 运算符重载使得类拦截常规的Python运算。
- 类可以重载所有Python表达式的运算符。
- 类也可以重载打印,函数调用,属性点号运算等内置运算。
- 重载使得类实例行为像内置类型。
- 重载是通过特殊名称的类方法来实现的。
类中的构造函数,就是使用最多的运算符重载。
class Number:
def __init__(self, start):
self.data = start
def __sub__(self, other):
return Number(self.data - other)
def main():
X = Number(5)
Y = X - 2
print(Y.data)
if __name__ == "__main__":
main()
# 输出:
# 3
常见的运算符重载方法
方法 | 重载 | 调用 |
__init__ | 构造函数 | 对象建立: X = Class(args) |
__del__ | 析构函数 | X对象收回 |
__add__ | 运算符+ | 如果没有__iadd__, X + Y, X += Y |
__or__ | 运算符 | (按位或) |
__repr__, __str__ | 打印, 转换 | print(X), repr(X), str(X) |
__call__ | 函数调用 | X(*args, **kargs) |
__getattr__ | 点号运算 | X.undefined |
__setattr__ | 属性赋值语句 | X.any = value |
__delattr__ | 属性删除 | del X.any |
__getattribute__ | 属性获取 | X.any |
__getitem__ | 索引运算 | X[key], X[i:j], 没__iter__时的for循环和其他迭代器 |
__setitem__ | 索引赋值语句 | X[key] = value, X[i:j] = sequence |
__delitem__ | 索引和分片删除 | def X[key], def X[i:j] |
__len__ | 长度 | len(X), 如果没有bool, 真值测试 |
__bool__(Python 3.0及之后版本), __nonzero__(Python 2.6及之后版本) | 布尔测试 | bool(X),nonzero(X) |
__lt__, __gt__, __le__, __ge__ | 大小比较 | X < Y, X > Y, X <= Y, X >= Y |
__eq__, __ne__(Python 3.0及之后版本), __cmp__(Python 2.6及之后版本) | 等值比较 | X == Y, X != Y |
__radd__ | 右侧加法 | other + X |
__iadd__ | 实地加法 | X += Y |
__iter__, __next__ | 迭代环境 | I = iter(X) next(I), 循环, map(F, X) |
__contains__ | 成员关系测试 | item in X |
__index__ | 整数值 | hen(X), bin(X), oct(X), O[X], O[X:] |
__enter__, __exit__ | 环境管理器 | with obj as var: |
__get__, __set__ | 描述符属性 | X.attr, X.attr = value, del X.attr |
__delete__ | del操作触发 | del(X)时执行 |
__new__ | 创建 | 在__init__之前创建对象 |
运算符重载的常见例子
索引和分片:__getitem__和__setitem__
- 对于实例的索引和分片,会调用__getitem__方法。
- 对于索引和分片赋值,会调用__setitem__方法。
- 对于索引迭代,成员关系测试in,列表解析,内置函数map,列表和元素赋值运算以及类型构造方法也会自动调用__getitem__。
class Indexer:
data = [5, 6, 7, 8, 9]
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, key, value):
self.data[key] = value
def main():
X = Indexer()
print(X[0])
print(X[2:4])
print(X[1:])
X[0] = 1
for item in X:
print(item)
print(1 in X)
print([num for num in X])
print(list(map(oct, X)))
(a, b, c, d, e) = X
print('---------------')
print(a)
print(b)
print(list(X))
print(tuple(X))
X.data = 'abcdefg'
print(''.join(X))
if __name__ == "__main__":
main()
'''
输出:
5
[7, 8]
[6, 7, 8, 9]
1
6
7
8
9
True
[1, 6, 7, 8, 9]
['0o1', '0o6', '0o7', '0o10', '0o11']
---------------
1
6
[1, 6, 7, 8, 9]
(1, 6, 7, 8, 9)
abcdefg
'''
迭代器对象:__iter__和__next__
普通迭代器的对象
- 在Python中,迭代环境是通过调用内置函数iter去尝试寻找__iter__方法实现的,返回一个迭代器对象。
- 如果有iter方法,Python就会重复地调用这个迭代器对象的next方法,直到发生StopIteration异常。如果没有这一类方法,才会去寻找__getitem__方法。注意,__iter__只循环一次,而不是循环多次。
class Squares:
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value ** 2
def main():
X = Squares(1, 5)
# I = iter(X) # 手动迭代也有效
print(next(X))
# X[1] # TypeError: 'Squares' object is not subscriptable,因为没有实现__getitem__方法
print([n for n in X])
print([n for n in X])
print([n for n in Squares(1,5)])
print('--------------------------')
X = Squares(1, 5)
I1 = iter(X)
I2 = iter(X)
print(next(I1))
print(next(I2))
print(next(I1))
print(next(I2))
if __name__ == "__main__":
main()
'''
输出:
1
[4, 9, 16, 25]
[]
[1, 4, 9, 16, 25]
--------------------------
1
4
9
16
'''
有多个迭代器的对象
- 用类编写用户迭代器的时候,可以自己决定是支持单一的还是多个的迭代器对象。
class Squares1:
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value ** 2
class Squares2:
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
return Squares2(self.value + 1, self.stop)
def __next__(self):
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value ** 2
def main():
X = Squares1(1, 5)
I1 = iter(X)
I2 = iter(X)
print(next(I1))
print(next(I2))
print(next(I1))
print(next(I2))
print('------------------------------------')
Y = Squares2(1, 5)
I1 = iter(Y)
I2 = iter(Y)
print(next(I1))
print(next(I2))
print(next(I1))
print(next(I2))
if __name__ == "__main__":
main()
'''
输出:
1
4
9
16
------------------------------------
1
1
4
4
'''
成员关系:__contains__, __iter__和__getitem__
在Python的迭代领域,类通常把in成员关系运算符实现为一个迭代,使用__iter__方法或者__getitem__方法,也可以编写一个__contains__方法。优先级为__contains__ > __iter__ > __getitem__。
class Iters:
def __init__(self, value):
self.data = value
def __getitem__(self, i):
print('get[%s]: ' % i, end='')
return self.data[i]
def __iter__(self):
print('iter=> ', end='')
self.ix = 0
return self
def __next__(self):
print('next: ',end='')
if self.ix == len(self.data): raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
def __contains__(self, item):
print('contains: ', end='')
return item in self.data
def main():
X = Iters([1, 2, 3, 4, 5])
print(3 in X)
for i in X:
print(i, end=' | ')
print()
print([i ** 2 for i in X])
print(list(map(bin, X)))
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
if __name__ == "__main__":
main()
'''
输出:
contains: True
iter=> next: 1 | next: 2 | next: 3 | next: 4 | next: 5 | next:
iter=> next: next: next: next: next: next: [1, 4, 9, 16, 25]
iter=> next: next: next: next: next: next: ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next: 1 @ next: 2 @ next: 3 @ next: 4 @ next: 5 @ next:
'''
class Iters:
def __init__(self, value):
self.data = value
def __getitem__(self, i):
print('get[%s]: ' % i, end='')
return self.data[i]
def __iter__(self):
print('iter=> ', end='')
self.ix = 0
return self
def __next__(self):
print('next: ',end='')
if self.ix == len(self.data): raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
'''
def __contains__(self, item):
print('contains: ', end='')
return item in self.data
'''
def main():
X = Iters([1, 2, 3, 4, 5])
print(3 in X)
for i in X:
print(i, end=' | ')
print()
print([i ** 2 for i in X])
print(list(map(bin, X)))
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
if __name__ == "__main__":
main()
'''
输出:
iter=> next: next: next: True
iter=> next: 1 | next: 2 | next: 3 | next: 4 | next: 5 | next:
iter=> next: next: next: next: next: next: [1, 4, 9, 16, 25]
iter=> next: next: next: next: next: next: ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next: 1 @ next: 2 @ next: 3 @ next: 4 @ next: 5 @ next:
'''
class Iters:
def __init__(self, value):
self.data = value
def __getitem__(self, i):
print('get[%s]: ' % i, end='')
return self.data[i]
def __iter__(self):
print('iter=> ', end='')
self.ix = 0
return self
def __next__(self):
print('next: ',end='')
if self.ix == len(self.data): raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
'''
def __contains__(self, item):
print('contains: ', end='')
return item in self.data
'''
def main():
X = Iters([1, 2, 3, 4, 5])
print(3 in X)
for i in X:
print(i, end=' | ')
print()
print([i ** 2 for i in X])
print(list(map(bin, X)))
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
if __name__ == "__main__":
main()
'''
输出:
iter=> next: next: next: True
iter=> next: 1 | next: 2 | next: 3 | next: 4 | next: 5 | next:
iter=> next: next: next: next: next: next: [1, 4, 9, 16, 25]
iter=> next: next: next: next: next: next: ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next: 1 @ next: 2 @ next: 3 @ next: 4 @ next: 5 @ next:
'''
class Iters:
def __init__(self, value):
self.data = value
def __getitem__(self, i):
print('get[%s]: ' % i, end='')
return self.data[i]
'''
def __iter__(self):
print('iter=> ', end='')
self.ix = 0
return self
def __next__(self):
print('next: ',end='')
if self.ix == len(self.data): raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
def __contains__(self, item):
print('contains: ', end='')
return item in self.data
'''
def main():
X = Iters([1, 2, 3, 4, 5])
print(3 in X)
for i in X:
print(i, end=' | ')
print()
print([i ** 2 for i in X])
print(list(map(bin, X)))
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
if __name__ == "__main__":
main()
'''
输出:
get[0]: get[1]: get[2]: True
get[0]: 1 | get[1]: 2 | get[2]: 3 | get[3]: 4 | get[4]: 5 | get[5]:
get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: [1, 4, 9, 16, 25]
get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: ['0b1', '0b10', '0b11', '0b100', '0b101']
get[0]: 1 @ get[1]: 2 @ get[2]: 3 @ get[3]: 4 @ get[4]: 5 @ get[5]:
'''
属性引用:__getattr__和__setattr__
- __getattr__方法拦截属性点号运算,当通过未定义属性名称和实例进行点号运算时,就会用属性名称作为字符串调用这个方法。
- __setattr__会拦截所有属性的赋值语句,如果定义了这个方法,self.attr = value会变成self.__setattr__(‘attr’, value)。注意,这导致了对任何self属性做赋值,都会再调用__setattr__,也就是说,在函数中需要对self.__dict__进行赋值运算。使用self.__dict__[‘name’] = x,而不是self.name = x。
class accesscontrol:
def __setattr__(self, key, value):
if key == 'age':
self.__dict__[key] = value
elif key == 'name':
self.__dict__[key] = value
else:
raise AttributeError(key + ' not allowed')
def __getattr__(self, item):
if item in self.__dict__:
return self.__dict__[item]
elif item == 'age':
return 0
elif item == 'name':
return ''
else:
raise AttributeError(item)
def main():
X = accesscontrol()
X.age = 100
X.name = 'Mike'
print(X.age)
print(X.name)
# X.gender = 'mile' # AttributeError: gender not allowed
X.__dict__['gender'] = 'mile'
print(X.gender)
if __name__ == "__main__":
main()
'''
输出:
100
Mike
mile
'''
返回字符串表达式:__str__和__repr__
- 打印实例操作会首先尝试__str__和str内置函数。
- __repr__用于任何地方,除了当定义了一个__str__之后。如果没有__str__,打印会只用__repr__。
- 为了确保一个定制显示在所有环境中都显示而不管容器是什么,建议使用__repr__,而不是__str__。
class Printer:
def __init__(self, val):
self.val = val
def __str__(self):
return str(self.val)
#def __repr__(self):
# return str(self.val)
def main():
objs = [Printer(2), Printer(3)]
for x in objs:
print(x)
print(objs)
if __name__ == "__main__":
main()
'''
输出:
2
3
[<__main__.Printer object at 0x000001F208DB94C0>, <__main__.Printer object at 0x000001F20AA0E370>]
'''
class Printer:
def __init__(self, val):
self.val = val
# def __str__(self):
# return str(self.val)
def __repr__(self):
return str(self.val)
def main():
objs = [Printer(2), Printer(3)]
for x in objs:
print(x)
print(objs)
if __name__ == "__main__":
main()
'''
输出:
2
3
[2, 3]
'''
加法,右侧加法和原处加法:__add__, __radd__和__iadd__
- __add__方法支持左侧加法和原处加法。
- __radd__支持右侧加法。
- __iadd__支持原处加法。注意__iadd__只支持原处加法。
- 原处加法的好处是可以直接对实例的属性进行修改,而其他加法则需要返回加法操作的结果。
- 同样的操作可以推广到乘法,右侧乘法和原处乘法:__mul__, __rmul__和__imul__。
class Add1:
def __init__(self, val):
self.val = val
def __add__(self, other):
self.val += other
return self
class Add2:
def __init__(self, val):
self.val = val
def __add__(self, other):
return self.val + other
class Radd:
def __init__(self, val):
self.val = val
def __radd__(self, other):
return other + self.val
class Iadd:
def __init__(self, val):
self.val = val
def __iadd__(self, other):
self.val += other
return self
def main():
x = Add1(5)
x += 1
print(x.val)
x = x + 1
print(x.val)
x = Add2(5)
x += 1
print(x)
x = x + 1
print(x)
x = Radd(5)
x = 1 + x
print(x)
x = Iadd(5)
x += 1
print(x.val)
if __name__ == "__main__":
main()
'''
输出:
6
7
6
7
6
6
'''
比较:__lt__,__gt__与其他方法
- 六种比较运算符:
<, >, <=, >=, ==, !=分别对应__lt__,__gt__, __le__, __ge__, __eq__和__ne__这几种方法。
class Compare:
data = 'abcde'
def __lt__(self, other):
return self.data < other
def __gt__(self, other):
return self.data > other
def __le__(self, other):
return self.data <= other
def __ge__(self, other):
return self.data >= other
def __eq__(self, other):
return self.data == other
def __ne__(self, other):
return self.data != other
def main():
X = Compare()
print('abcde' == X)
print('xyz' == X)
print('abcde' != X)
print('xyz' != X)
print('abc' < X)
print('xyz' < X)
print('abc' > X)
print('xyz' > X)
print('abc' <= X)
print('xyz' <= X)
print('abc' >= X)
print('xyz' >= X)
if __name__ == "__main__":
main()
'''
输出:
True
False
False
True
True
False
False
True
True
False
False
True
'''
布尔测试:__bool__和__len__
- 在获取实例的真假信息时,Python 3.0及之后的版本会先尝试__bool__,再尝试__len__,Python 2.6及之后的版本会先尝试__len__,再尝试__bool__。
- __len__还可以配合len(X)函数使用,一般用来返回实例对象中字符串,列表,元组,字典等数据的长度。
class Bool:
data = 'abcde'
def __bool__(self):
if self.data:
return True
return False
class Len1:
data = 'abcde'
def __len__(self):
if self.data:
return True
return False
class Len2:
data = 'abcde'
def __len__(self):
return len(self.data)
def main():
X = Bool()
print(bool(X))
X = Len1()
print(bool(X))
X = Len2()
print(len(X))
if __name__ == "__main__":
main()
'''
输出:
True
True
5
'''
对象析构函数:__del__
- 每当实例空间被收回时,__del__会自动执行,称为析构函数。
- 析构函数不常用,因为Python在实例收回时,会自动地收回该实例所拥有的所有空间,对于空间管理来说不需要析构函数。并且,在大的项目中,往往无法轻易地预测实例何时收回。
class Life:
def __init__(self, name='unknow'):
print('Hello', name)
self.name = name
def __del__(self):
print('Goodbye', self.name)
def main():
X = Life('John')
del(X)
if __name__ == "__main__":
main()
'''
输出:
Hello John
Goodbye John
'''
Call表达式:__call__
- 如果有__call__定义,Python就会为实例应用函数调用表达式运行__call__方法。这使得类实例的用法贴近函数。
- __call__支持所有的函数参数传递方式,意味着带有一个__call__的类和实例,支持与常规函数和方法完全相同的参数语法和语义。
class Prod:
def __init__(self, value):
self.value = value
def __call__(self, *args):
return self.value * sum(args)
def main():
X = Prod(2)
print(X(3, 4, 5, 6))
if __name__ == "__main__":
main()
'''
输出:
36
'''
以上,欢迎各位读者朋友提出意见或建议。