1:运算符重载介绍
运算符重载,就是在某个类的方法中,拦截其内置的操作(比如:+,-,*,/,比较,属性访问,等等),使其实例的行为接近内置类型。
当类的实例出现在内置操作中时(比如:两个实例相加 +),Python会自动调用你的方法(比如:你重载的__add__方法),并且你的方法的返回值会作为相应操作的结果。
Python3中的运算符重载:
运算符重载让类拦截常规的Python操作。
类可以重载所有Python表达式运算符。
类也可以重载打印,函数调用,属性访问等内置运算。
重载是通过在一个类中提供特殊名称的方法来实现的。
2:Python3中常见运算符重载方法
3:运算符重载方法示例
3.1:索引和分片:__getitem__和__setitem__
在实例进行 类似 X[2] 这种操作时会调用__getitem__方法;
在实例进行 类似 X[2] = value 这种操作时会调用__setitem__方法;索引:
# encoding=gbk class Test: def __getitem__(self, item): print('item:',item) return item**3 # 返回 x 的三次方 def __setitem__(self, key, value): print(key,value) t = Test()print(t[2]) # 会调用__getitem__函数, 返回2 的三次方print(t[3]) # 会调用__getitem__函数, 返回3 的三次方 t[3] = 100 # 会调用__setitem__
分片:
# encoding=gbk class Test: def __getitem__(self, item): print('item:',item) if isinstance(item,int): return item**3 # 返回 x 的三次方 else: print('slicing',item.start,item.stop,item.step) return [x**3 for x in range(item.start,item.stop,item.step)] def __setitem__(self, key, value): print(key,value) # do something t = Test()# 索引:print(t[2]) # 会调用__getitem__函数, 返回2 的三次方print(t[3]) # 会调用__getitem__函数, 返回3 的三次方t[3] = 100 # 会调用__setitem__ print('*'*60)# 分片:print(t[2:10:2]) # 传入的是分片对象t[2:5] = 100
3.2:返回数值:__index__ (__index__不是索引)
在需要整型数字的上下文中,会调用__index__函数,__index__会为实例返回一个整数值。比如:调用函数hex(X),bin(X)时,会去调用X的__index__方法:
# encoding=gbk class Test: def __index__(self): return 100 X = Test()print(hex(X))print(bin(X))print(oct(X))
3.3:可迭代对象:__iter__,__next__
如果要使自己定义的类的对象是可迭代的,那么就必须使这个类支持迭代协议,即重载__iter__,__next__方法。
迭代协议:(包括两种对象)
可迭代对象(Iterable):里面包含了__iter__(); 可迭代对象X, 通过调用 I = iter(X) 可返回一个迭代器对象,再调用next(I) 就可以迭代X中的元素。
迭代器对象(Iterator):里面包含了__iter__() 和 __next__()
迭代过程:(for循环,等迭代中默认的操作)
首先调用 iter函数: I = iter(X); 调用的是X.__iter__()
然后对返回对象I调用next:next(I); 调用的是 I.__next__(),直到迭代完成。
3.3.1:单遍迭代
即只能迭代一次:
# encoding=gbk class Fibonacci: def __init__(self, n): self.a = 0 self.b = 1 self.max_cnt = n def __iter__(self): return self def __next__(self): self.a, self.b = self.b, self.a + self.b if self.a > self.max_cnt: raise StopIteration return self.a fib = Fibonacci(5)print(fib) print('1:'+'*' * 30)I = iter(fib) # 调用 fib.__iter__(),(返回的是self,及fib)print(next(I)) # 调用的是 fib.__next__(),fib对象中的a,b属性值会改变。print(next(I)) # 调用的是 fib.__next__() print('1:'+'*' * 30) # for循环:首先调用的是I = iter(fib),此处返回的是self即fib,再调用next(I),即fib.__next__(),此时其值已经取完2个了,因此从第3个开始取。for x in fib: print(x)print('2:'+'*' * 30)# 此处与上面的循环一样,但是fib.__next__()已经把数据取完了,故这里不会有输出!for x in fib: print(x) print('3:'+'*' * 30) #i = iter(fib) # 返回self,即fibfor ii in i: # 与上面的 for x in fib 一样,不会再输出! print(ii)
3.3.2:多遍迭代
即可以多次迭代使用:
# encoding=gbk class Fibonacci: def __init__(self, n): self.a = 0 self.b = 1 self.max_cnt = n def __iter__(self): return FibonacciIter(self.a,self.b,self.max_cnt) class FibonacciIter: def __init__(self,a,b,max_cnt): self.a = a self.b = b self.max_cnt = max_cnt def __next__(self): self.a, self.b = self.b, self.a + self.b if self.a > self.max_cnt: raise StopIteration return self.a fib = Fibonacci(5) I = iter(fib)print(next(I)) # 调用的是FibonacciIter对象中的__next__方法,fib对象中的a,b属性没有任何变化。print(next(I))print(next(I))print(next(I))print(next(I)) # 迭代完了# print(next(I)) # 上一步迭代完了,再次调用next(I)会抛出异常, print('1:'+'*' * 30) print(fib)print('1:'+'*' * 30)# for循环(默认调用):首先调用一次 I = iter(fib) 即fib.__iter__(),返回一个重新创建FibonacciIter对象, ;# 然后再调用 next(I),即I.__next__() (也就是FibonacciIter类中的__next__()函数),直到迭代完。for x in fib: print(x)print('2:'+'*' * 30)# 与上面for循环调用过程一样。for x in fib: print(x) print('3:'+'*' * 30) # i = iter(fib)# for ii in i: # 这里会报错,因为iter(fib)返回FibonacciIter的实例 i , # 而for循环首先会调用 iter(i) 即 调用FibonacciIter中的__iter__函数,而FibonacciIter类中没有重载此函数;# print(ii)
3.3.3:__iter__ 加 yield 实现多遍迭代
# encoding=gbk class Test: def __init__(self,start,stop): self.start = start self.stop = stop def __iter__(self): print(self.start,self.stop+1) for value in range(self.start,self.stop+1): yield value**2 t = Test(1,3)# 说明:for循环中,首先调用 I = iter(t),即调用的是t.__iter__(),在__iter__函数中有yield语句,# yield语句会自动创建一个包含 __next__ 方法的类,并返回它的实例,# 然后会调用 next(I),I 为yield自动创建类的实例for i in t: print(i)print('*'*40)for i in t: print(i)
3.4:属性访问:__getattr__,__getattribute__和__setattr__
3.4.1 __getattr__,__getattribute__
__getattr__ 会拦截未定义的属性,即在使用点号访问属性时(如:X.属性) ,如果Python通过其继承树搜索过程中没有找到这个属性,那么就会自动调用__getattr__方法。
__getattribute__ 会拦截所有属性。
# encoding=gbk class Test: aa = 0 def __init__(self): self.age = 100 def __getattr__(self, item): print('in __getattr__:',item) t = Test()print('1:' ,t.__dict__)# 属性引用,属性找不到时,就会调用__getattr__方法print(t.aa) # 在类中存在类属性 aa,print('2:'+ '*' * 30) print(t.bb) # t.__dict__ 中不存在属性 bb,其父类中也没有属性bb,故会调用__getattr__方法print('3:'+ '*' * 30)print(t.age) # 存在实例属性age,不会调用__getattr__方法t.age = 200print('4:' ,t.__dict__) """1: {'age': 100}02:******************************in __getattr__: bbNone3:******************************1004: {'age': 200}"""
3.4.2__setattr__
__setattr__:会拦截所有的属性赋值
如果定义或者继承了__setattr__方法,那么 self.attr = value,将会变成 self.__setattr__('attr',value)
这里要注意的是 如果在__setattr__方法中有使用 self.attr = value 的赋值形式,那么__setattr__将会进入死循环,因为self.attr = value 的赋值形式会调用self.__setattr__('attr',value),而__setattr__方法中又使用self.attr = value进行赋值,从而进入一个循环。
# encoding=gbk class Test: def __init__(self): # 构造函数中对 self.age 进行赋值,如果继承了__setattr__方法, # 就会把self.age = 100 变成 self.__setattr__('age',100) self.age = 100 def __getattr__(self, item): print('in __getattr__:',item) def __setattr__(self, key, value): print('in __setattr__:',key,value) # self.aa = 100 # 这样赋值会导致死循环,因为 self.aa = 100 会变成 self.__setattr__('aa',100),而后者又调用了前者 if key != 'age': # 拦截 age属性 self.__dict__[key] = value t = Test()print('1:' + '*'*30)print(t.__dict__) # 此处输出为{};虽然在构造函数中有self.age = 100赋值,但是在 __setattr__方法中把它过滤掉啦print('2:' + '*'*30)print(t.age) # 由于 age属性被拦截掉了,故访问t.age会调用 __getattr__方法print('3:' + '*'*30)t.age = 200 # 会把 age给拦截掉t.name = 'ixusy' # 不存在name属性,因此会调用__setattr__,在__setattr__方法中把 name属性 添加到属性字典__dict__中, # 后面就可以通过使用t.name进行访问。print('4:' + '*'*30)print(t.__dict__)print('5:' + '*'*30) """输出结果:in __setattr__: age 1001:******************************{}2:******************************in __getattr__: ageNone3:******************************in __setattr__: age 200in __setattr__: name ixusy4:******************************{'name': 'ixusy'}5:******************************"""
3.4.3 __getattr__ 和 __setattr__总结
__getattr__ :拦截不存在的属性引用!
__setattr__:拦截所有的属性赋值,当心死循环!
3.5:调用表达式:__call__
在实例上执行函数调用表达式,就会自动调用__call__函数
# encoding=gbk class Test: def __call__(self, *args, **kwargs): print('call:',args,kwargs) t = Test()t(1,2,3)t(1,2,3,b=22)# 传递参数,需要符合函数传递参数的规则# t(1,a=2,3,b=22) # 这样传递会报错
3.6:字符串显示:__str__ 和 __repr__
__str__ 和 __repr__ 都是用于显示字符串,只不过是他们的使用场景不同而已。
__str__ :打印操作(print),内置函数str调用,会优先调用__str__ ,如果没有重载__str__,就会去调用__repr__;
__repr__:用于所有其他场景:包括交互式命令行,repr函数,嵌套显示,以及没有可用__str__时,print和str的调用。
__repr__ 可用于任何地方,__str__用于print 和 str函数。
3.7:比较运算
# encoding=gbk class Person: def __init__(self,name,age,height): self.name = name self.age = age self.height = height # 比较规则可以自行定义, # 下面规则为: # 1:年龄小的 比较结果为小 # 2:年龄相等的,比较身高:身高小,结果为小 # 3:其他情况返回False def __lt__(self, other): if self.age < other.age: return True elif self.age == other.age: return self.height < other.height else: return False """ 还可以重载: __gt__ __le__ __ge__ __eq__ __ne__ 需要注意的是 p1 == p2,并不表示p1 != p2, 具体要看你怎么实现 __eq__,__ne__方法, 实际中尽可能使得__eq__,__ne__方法的实现符合正常的逻辑。 """ p1 = Person('ixusy88',18,188)p2 = Person('i',18,180)print(p1 < p2) # False p1 = Person('ixusy88',18,177)p2 = Person('i',18,180)print(p1 < p2) # True