运算符重载

运算符重载概念

  1. 运算符重载让类拦截常规的python运算;
  2. 类可重载所有python表达式运算符;
  3. 类也可重载打印,函数调用,属性点号运算等内置运算;
  4. 重载是类实例的行为像内置类型;
  5. 重载是通过提供特殊名称的类方法来实现的.

常见的运算符重载方法

__bool__  | 布尔测试  | bool(X),真测试lt,gt,le,ge,eq,ne| 特定的比较  | XY…radd| 右侧加法  | Other + Xiadd| 增强的加法  | X += Yiter,next| 迭代环境  | I=iter(X),next(I)contains | 成员关系测试  | item in X(任何可迭代对象)index| 整数值  | hex(X),bin(X),oct(X),o[X],O[X:]enter,exit| 环境管理器  | with obj as var:get,set,delete| 描述符属性  | X.attr,X.attr=Value,del X.attrnew| 创建  | init`之前创建对象

所有重载方法的名称前后都有两个下划线字符,以便把同类中定义的变量名区别开来

构造函数和表达式: __init____sub__

>>> class Number:
...   def __init__(self,start):
...     self.data = start
...   def __sub__(self,other):
...     return Number(self.data - other)
...
>>> X = Number(5)
>>> Y = X - 2
>>> Y
<__main__.Number object at 0x101bdd940>
>>> Y.data
3

索引和分片: __getitem____setitem__

基本索引

>>> class Index:
...   def __getitem__(self,item):
...     return item ** 2
...
>>> for i in range(5):
...   I = Index()
...   print(I[i],end=' ')
...
0 1 4 9 16 >>>

切片索引

>>> class Index:
...   data = [5,6,7,8,9]
...   def __getitem__(self,item):
...     print('getitem: ',item)
...     return self.data[item]
...   def __setitem__(self,key,value):
...     self.data[key] = value
...
>>> X = Index()
>>> print(X[1:4])
getitem:  slice(1, 4, None)
[6, 7, 8]
>>> X[1:4] = (1,1,1)
>>> print(X[1:4])
getitem:  slice(1, 4, None)
[1, 1, 1]

索引迭代: __getitem__

如果重载了这个方法,for循环每次循环时都会调用类的getitem方法

>>> class stepper:
...   def __getitem__(self,item):
...     return self.data[item].upper()
...
>>> X = stepper()
>>> X.data = 'yang'
>>> for item in X:
...   print(item)
...
Y
A
N
G

迭代器对象: __iter____next__

>>> 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
...
>>> for i in Squares(1,5):
...   print(i)
...
1
4
9
16
25

成员关系: __contains__ , __iter____getitem__

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

class Iters:
    def __init__(self,value):
        self.data = value

    def __getitem__(self,item):
        print('get[%s]' % item,end='')
        return self.data[item]

    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

X = Iters([1,2,3,4,5])
print(3 in X)
for i in X:
    print(i,end='|')

print([i ** 2 for i in X])
print(list(map(bin,X)))

I = iter(X)
while True:
    try:
        print(next(I),end='@')
    except StopIteration as e:
        break

属性引用: __getattr____setattr__

当通过未定义的属性名称和实例通过点号进行访问时,就会用属性名称作为字符串调用这个方法,但如果类使用了继承,并且在超类中可以找到这个属性,那么就不会触发.

>>> class empty:
...   def __getattr__(self,item):
...     if item == 'age'
  File "<stdin>", line 3
    if item == 'age'
                   ^
SyntaxError: invalid syntax
>>> class empty:
...   def __getattr__(self,item):
...     if item == 'age':
...       return 18
...     else:
...       raise AttributeError(item)
...
>>> x = empty()
>>> print(x.age)
18
>>> print(x.name)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __getattr__
AttributeError: name
>>> class accesscontrol:
...   def __setattr__(self,key,value):
...     if key == 'age':
...       self.__dict__[key] = value
...     else:
...       raise AttributeError(key + ' not allowed')
...
>>> x = accesscontrol()
>>> x.age = 14
>>> print(x.age)
14
>>> x.name = "Hello"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
AttributeError: name not allowed

__repr____str__会返回字符串表达式

__repr____str__都是为了更友好的显示,具体来说,如果在终端下print(Class)则会调用__repr__,非终端下会调用__str__方法,且这两个方法只能返回字符串

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

class adder:
    def __init__(self,value=0):
        self.data = value

    def __add__(self,other):
        self.data += other

    def __repr__(self):
        return 'addrepr(%s)' % self.data

    def __str__(self):
        return 'N: %s' % self.data

x = adder(2)
x + 1
print(x)
print((str(x),repr(x)))
➜  python_test python3 027-exercise-2.py
N: 3
('N: 3', 'addrepr(3)')

右侧加法和原处加法: __radd____iadd__

只有当+右侧的对象时类实例,而左边对象不是类实例的时候,python才会调用__radd__

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

class Commuter:
    def __init__(self,val):
        self.val = val
    def __add__(self,other):
        print('add',self.val,other)
        return self.val + other
    def __radd__(self,other):
        print('radd',self.val,other)
        return other + self.val
x = Commuter(88)
y = Commuter(99)
print(x + 1)
print('')
print(1 + y)
print('')
print(x + y)

执行结果

➜  python_test python3 027-exercise-3.py
add 88 1
89

radd 99 1
100

add 88 <__main__.Commuter object at 0x101b9ff98>
radd 99 88
187

使用__iadd__进行原处加法

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

class Number:
    def __init__(self,val):
        self.val = val
    def __iadd__(self,other):
        self.val += other
        return self
x = Number(5)
x += 1
x += 1
print(x.val)

class Number:
    def __init__(self,val):
        self.val = val

    def __add__(self,other):
        return Number(self.val + other)

x = Number(5)
x += 1
x += 1
print(x.val)

Call表达式: __call__

当调用类实例时执行__call__方法

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

class Callee:
    def __call__(self,*args,**kwargs):
        print('Callee:',args,kwargs)
C = Callee()
C(1,2,3)
C(1,2,3,x=1,y=2,z=3)

class Prod:
    def __init__(self,value):
        self.value = value
    def __call__(self,other):
        return self.value * other

x = Prod(3)
print(x(3))
print(x(4))

输出

➜  python_test python3 027-exercise-5.py
Callee: (1, 2, 3) {}
Callee: (1, 2, 3) {'y': 2, 'z': 3, 'x': 1}
9
12

比较: __lt__ , __gt__和其他方法

类可以定义方法来捕获所有的6种比较运算符: < , > , <= , >= , == 和 !=

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

class C:
    data = 'spam'

    def __gt__(self,other):
        return self.data > other

    def __lt__(self,other):
        return self.data < other

x = C()
print(x > 'han')
print(x < 'han')

输出

➜  python_test python3 027-exercise-6.py
True
False

布尔值测试: bool 和 len

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

class Truth:
    def __bool__(self):
        return True
X = Truth()
if X: print('yes')

class Truth:
    def __bool__(self):
        return False

X = Truth()
print(bool(X))

输出

➜  python_test python3 027-exercise-7.py
yes
False

如果没有这个方法,python退而求其次的求长度,因为一个非空对象看作是真

>>> class Truth:
...   def __len__(self): return 0
...
>>> X = Truth()
>>> if not X : print('no')
...
no

如果两个方法都有,__bool__会胜过__len__

>>> class Truth:
...   def __bool__(self): return True
...   def __len__(self): return 0
...
>>> X = Truth()
>>> bool(X)
True

如果两个方法都没有定义,对象毫无疑义的看作为真

>>> class Truth: pass
...
>>> bool(Truth)
True

对象解析函数: __del__

每当实例产生时,就会调用init构造函数,每当实例空间被收回时,它的对立面__del__,也就是解析函数,就会自动执行

>>> class Life:
...   def __init__(self,name='unknown'):
...     print('Hello,',name)
...     self.name = name
...   def __del__(self):
...     print('Goodbye',self.name)
...
>>> brian = Life('Brian')
Hello, Brian
>>> brian = 'loretta'
Goodbye Brian