運算符重載

之前面咱們定義過的Vector向量類爲例,討論重載運算符。python

運算符重載基礎

運算符重載本質上是函數調用。許多面向對象語言都支持運算符重載,如C++,若是使用得當,API會更好用,代碼也會更加易於閱讀。python也支持運算符重載,只是增長了一些限制:函數

* 不能重載內置類型的運算符測試

* 不能新建運算符,只能重載已有的spa

* 某些運算符不能重載 --is、and、or和not(位運算符&、^、|能夠)scala

咱們首先在Vector重載四個算術運算符:一元運算符-和+,中綴運算符+和*。code

一元運算符

- (__neg__)component

  取負運算符,若是x是-2,那麼-x是2。對象

+ (__pos__)blog

  取正運算符,大部分狀況都是 x == +x。ip

~ (__invert__)

  對整數按位取反,定位爲 ~x == -(x+1),若是x是-2,那麼~x是1。

python把abs()函數也列爲一元運算符,它對應的特殊方法是__abs__。實現運算符的重載也就是實現這些特殊方法便可,這些方法直接輸self一個參數,代碼以下:

 1 from array import array
 2 class Vector:
 3     """
 4     自定義一個向量類
 5     """
 6     typecode = 'i'
 7     def __init__(self, items):
 8         self._components = array(self.typecode, items)
 9 
10     def __iter__(self):
11         return (i for i in (self._components))
12 
13     def __str__(self):
14         return 'Vector' + str(tuple(self)) #已經實現了__iter__方法,self實例是可迭代對象
15 
16     def __abs__(self):
17         return math.sqrt(sum(x*x for x in self))        
18 
19     def __neg__(self):
20         return Vector(-x for x in self)
21 
22     def __pos__(self):
23         return Vector(self)
24 
25     def __invert__(self):
26         return Vector(~x for x in self)
27 
28 v1 = Vector(range(10))
29 print(v1)
30 print(abs(v1))
31 
32 v2 = -v1
33 v3 = ~v1
34 print(v2)
35 print(v3)

重載向量加法運算符+

兩個向量相加獲得一個新向量,咱們不取嚴格意義的數學上的向量相加,咱們運行兩個維度不一樣的向量相加,獲得的新向量維度取較大的維度。

重載加法運算符就要實現__add__特殊方法:

 1 from array import array
 2 from itertools import zip_longest
 3 class Vector:
 4     """
 5     自定義一個向量類
 6     """
 7     typecode = 'i'
 8     def __init__(self, items):
 9         self._components = array(self.typecode, items)
10 
11     def __iter__(self):
12         return (i for i in (self._components))
13 
14     def __str__(self):
15         return 'Vector' + str(tuple(self)) #已經實現了__iter__方法,self實例是可迭代對象
16 
17     def __abs__(self):
18         return math.sqrt(sum(x*x for x in self))        
19 
20     def __neg__(self):
21         return Vector(-x for x in self)
22 
23     def __pos__(self):
24         return Vector(self)
25 
26     def __invert__(self):
27         return Vector(~x for x in self)
28 
29     def __add__(self, other):
30         """
31         兩個向量相加,向量維度取最長的一個
32         """
33         return Vector(x+y for x, y in zip_longest(self, other, fillvalue=0))
34 v1 = Vector(range(10))
35 print(v1)
36 print(abs(v1))
37 
38 v2 = -v1
39 v3 = ~v1
40 print(v2)
41 print(v3)
42 
43 v4 = v1 + v3
44 print(v4)
45 
46 v5 = v1 + Vector([1,3,5,8,-6])
47 print(v5)

zip會把兩個序列類型的元素放在一塊兒組成元組,可是回去序列個數少的爲準,多餘的元素就被丟棄;二itertools.zip_longest以元素個數多的爲準,fillvalue能夠指定缺乏元素時使用的默認值。

ps:不管是一元運算符仍是中綴運算符都不能修改原對象,都是返回一個新的對象。只有增量表達式可能會例外。

v1+v2等價於v1.__add__(v2),因此要求v1的類必須實現__add__方法,v2卻沒必要。

1 v6 = v1 + (10,20,30) # OK v1.__add__((10,20,30))
2 print(v6) 
3 
4 v7 = (10, 20, 30) + v1 # ERROR, tuple沒有__add__方法
5 print(v7)

能夠爲Vector增長__radd__方法便可。

1     def __radd__(self, other):
2     return self+other

單就表達式a+b來講,解釋器會執行以下操做:

1)、若是a有__add__方法,且返回值不是NotImplemented,那麼調用a.__add__(b)返回結果

2)、若是a沒有__add__方法,或者調用__add__方法返回NotImplemented,檢查b有沒有__radd__方法,若是有,且返回值不是NotImplemented,那麼調用b.__radd__(a),返回結果

3)、若是b沒有__radd__方法,或者__radd__方法返回NotImplemented,那麼拋出TypeError異常

__radd__是__add__的反向版本,又叫__add__的右向特殊方法,由於他在右操做數上調用。

__rsub__和__sub__的關係也是同樣。

不要把NotImplemented和NotImplementedError混淆。NotImplemented是個值,和None同樣,能夠做爲返回值返回,而NotImplementedError是異常,使用raise拋出。

__radd__直接藉助__add__實現是能夠的,任何知足交換律的類型均可以這麼作,可是向冪運算、除法運算、取模運算等不能夠。

Vector類對象能夠加可迭代對象,若是是不可迭代對象,就會拋出TypeError錯誤:

1 v1 + 3
2 """
3 運行結果
4   File "E:\test.py", line 1168, in <module>
5     v1 + 3
6   File "E:\test.py", line 1142, in __add__
7     return Vector(x+y for x, y in zip_longest(self, other, fillvalue=0))
8 TypeError: zip_longest argument #2 must support iteration
9 """

若是是可迭代對象,可是類型是浮點型也會拋出TypeError錯誤:

1 v1 + (1.0, 2.0, 3.0)
2 """
3 運行結果
4  File "E:\test.py", line 1118, in __init__
5     self._components = array(self.typecode, items)
6 TypeError: integer argument expected, got float
7 """

可是這樣的錯誤消息是因爲類型問題致使的,爲了遵照"鴨子類型"的精髓,咱們不能測試other的類型或者其包含的元素的類型,而是要捕獲TypeError異常,而後返回NotImplemented, 若是左操做數的__add__方法不能存在或者返回NotImplemented且右操做數的__radd__方法也不存在或者返回NotImplemented,那麼python纔會拋出TypError異常,返回一個錯誤提示如「unsupported operand type(s) for +: Vector and str」。

正確的實現以下:

 1 def __add__(self, other):
 2     """
 3     兩個向量相加,向量維度取最長的一個
 4     """
 5     try:
 6         return Vector(x+y for x, y in zip_longest(self, other, fillvalue=0))
 7     except TypeError:
 8         return NotImplemented
 9 
10 def __radd__(self, other):
11     return self+other

重載標量乘法運算符*

Vector([1,2,3])*x表示什麼呢?若是x是數字,那麼結果是個新的Vector實例,實例的每一個份量都會乘上x:

1 v1 = Vector([1, 2, 3])
2 v2 = v1*2
3 print(v2) #Vector(2, 4, 6)

若是x也是一個向量,即兩個向量相乘,叫作兩個向量的點積。若是把一個向量當作行向量,另外一個當成列向量,那麼就是向量乘法。NumPy等庫目前的作法是,不重載這兩種意義的*,只用*計算標量積,計算點積用numpy.dot()函數計算。

若是咱們本身實現標量積就要實現__mul__和__rmul__,代碼以下:

 1 from array import array
 2 from itertools import zip_longest
 3 import numbers, fractions
 4 class Vector:
 5     """
 6     自定義一個向量類
 7     """
 8     typecode = 'd'
 9     def __init__(self, items):
10         self._components = array(self.typecode, items)
11 
12     def __iter__(self):
13         return (i for i in (self._components))
14 
15     def __str__(self):
16         return 'Vector' + str(tuple(self)) #已經實現了__iter__方法,self實例是可迭代對象
17 
18     def __abs__(self):
19         return math.sqrt(sum(x*x for x in self))        
20 
21     def __neg__(self):
22         return Vector(-x for x in self)
23 
24     def __pos__(self):
25         return Vector(self)
26 
27     def __invert__(self):
28         return Vector(~x for x in self)
29 
30     def __add__(self, other):
31         """
32         兩個向量相加,向量維度取最長的一個
33         """
34         try:
35             return Vector(x+y for x, y in zip_longest(self, other, fillvalue=0))
36         except TypeError:
37             return NotImplemented
38     def __radd__(self, other):
39         return self+other
40 
41     def __mul__(self, scalar):
42         """
43         計算標量積
44         """
45         if not isinstance(scalar, numbers.Real):
46             return NotImplemented
47         return Vector(scalar*x for x in self)
48 
49     def __rmul__(self, scalar):
50         return self*scalar
51 v1 = Vector(range(10))
52 print(v1)
53 print('------------')
54 v2 = v1*3
55 print(v2)
56 
57 v3 = 2*v2
58 print(v3)
59 
60 v4 = False*v2
61 print(v4)
62 
63 v5 = v2*fractions.Fraction(3, 4)
64 print(v5)

在這裏使用了跟重載加法運算符時不同的辦法,使用「白鵝類型」,即用instance檢查scalar的類型。

scalar容許是整型、浮點型、bool型、甚至是fractions.Fraction實例,但不能是複數。

除此以外,還有其餘的運算符:

還有沒有列出的比較運算符。(小於<, 小於等於<=等等)

 ps:點積運算符是python3.5新增長的。在舊版本中並不支持。

比較運算符

python解釋器對衆多比較運算符(==、!=、>、<、>= 、<=)的處理跟上面的相似,不過在兩個方面有重大區別。

* 正向和方向調用使用的是同一系列方法。例如,對==來講,正向和反向調用都是調用__eq__方法,只是把參數對調了;而正向的__gt__方法調用的是反向的__lt__方法,並把參數對調。

* 對==和!=來講,若是反向調用失敗,python會比較對象的id,而不是拋出TypeError。

若是正向方法返回NotImplemented的話,調用反向方法。

如今咱們來實現一下Vector類的==操做。

 1 from array import array
 2 from itertools import zip_longest
 3 import numbers, fractions
 4 class Vector:
 5     """
 6     自定義一個向量類
 7     """
 8     typecode = 'd'
 9     def __init__(self, items):
10         self._components = array(self.typecode, items)
11 
12     def __iter__(self):
13         return (i for i in (self._components))
14 
15     def __len__(self):
16         return len(self._components)
17 
18     def __str__(self):
19         return 'Vector' + str(tuple(self)) #已經實現了__iter__方法,self實例是可迭代對象
20 
21     def __abs__(self):
22         return math.sqrt(sum(x*x for x in self))        
23 
24     def __neg__(self):
25         return Vector(-x for x in self)
26 
27     def __pos__(self):
28         return Vector(self)
29 
30     def __invert__(self):
31         return Vector(~x for x in self)
32 
33     def __add__(self, other):
34         """
35         兩個向量相加,向量維度取最長的一個
36         """
37         try:
38             return Vector(x+y for x, y in zip_longest(self, other, fillvalue=0))
39         except TypeError:
40             return NotImplemented
41     def __radd__(self, other):
42         return self+other
43 
44     def __mul__(self, scalar):
45         """
46         計算標量積
47         """
48         if not isinstance(scalar, numbers.Real):
49             return NotImplemented
50         return Vector(scalar*x for x in self)
51 
52     def __rmul__(self, scalar):
53         return self*scalar
54 
55     def __eq__(self, other):
56         return len(self) == len(other) and \
57                 all(x==y for x, y in zip(self, other))
58 
59 v1 = Vector(range(10))
60 print(v1)
61 print(v1==[i for i in range(10)])
62 print(v1==[i for i in range(8)])
63 print(v1==Vector((1,2,3,4,5,6,7,8)))
64 print(v1==Vector((1,2,3,4,5,6,7,8,8)))
65 print((0,1,2,3,4,5,6,7,8,9) == v1)

對於向量類對象和元組的比較可能並不盡如人意,咱們能夠加點類型檢查:

1 def __eq__(self, other):
2     if not isinstance(other, Vector):
3         return NotImplemented
4     return len(self) == len(other) and \
5             all(x==y for x, y in zip(self, other))

!=運算符咱們無需定義,python會自動把對==運算符運行的結果取反。

增量賦值運算符

Vector已經支持增量運算符了。

1 va = Vector([1.0, 2.0, 3.0])
2 vb = va
3 print(id(vb), id(va)) #vb,  vb引用同一個對象
4 va+=[7,8,9,10,11]
5 print(va)
6 print(id(vb), id(va)) #vb仍是原對象,va是新建立的對象

若是一個類沒有實現就地運算符,增量運算符只是語法糖:a += b的做用跟a = a+b是同樣的,會建立一個新對象。若是實現了就地運算符方法,例如__iadd__,那麼 a += b不會建立新對象。

1 def __iadd__(self, other):
2     self._components = array(self.typecode,map(lambda pair:sum(pair),zip_longest(self, other, fillvalue=0)))
3     return self
4 va = Vector([1.0, 2.0, 3.0])
5 vb = va
6 print(id(vb), id(va)) #vb,  vb引用同一個對象
7 va+=[7,8,9,10,11]
8 print(va)
9 print(id(vb), id(va)) #vb,va的id同樣,證實並無建立新對象

__iadd__方法最後必定要把對象自身返回,若是沒有返回self的話,va += vb,那麼va就變成了None,由於若是函數沒有返回值,python默認返回None。

 1 def __iadd__(self, other):
 2     self._components = array(self.typecode,                    map(lambda pair:sum(pair), zip_longest(self, other, fillvalue=0)))
 3         #return self 
 4 va = Vector([1.0, 2.0, 3.0])
 5 vb = va
 6 print(id(vb), id(va)) #vb,  vb引用同一個對象
 7 va+=[7,8,9,10,11]
 8 print(va)
 9 print(id(vb), id(va))
10 print(va) #None

總結:

* python支持對運算符重載,但不包括內置的類型的運算符以及is、and、or、not。

* 一元運算符和中綴運算符的重載、正向方法和反向方法

* 使用鴨子類型捕獲TypeError異常,或者使用isinstance檢查類型,isinstance不能用具體類,而應該使用抽象基類,由於後續用戶自定義的類能夠生命爲抽象基類的子類或是註冊爲虛擬子類

* 比較運算符和增量運算符的重載

相關文章
相關標籤/搜索