python運算符重載


python對運算符重載的一些限制

1.不能重載內置類型的運算符

2.不能新建運算符,只能重載現有的

3.某些運算符不能重載:is、and、or、not

建立某Vector類

 1 from array import array
 2 import reprlib
 3 import math
 4 
 5 
 6 class Vector:
 7     typecode = 'd'
 8     shortcut_names = 'xyzt'
 9 
10     def __init__(self, components):
11         self._components = array(self.typecode, components)
12 
13     def __iter__(self):
14         return iter(self._components)
15 
16     def __repr__(self):
17         components = reprlib.repr(self._components)
18         components = components[components.find('['):-1]
19         return 'Vector({})'.format(components)
20 
21     def __str__(self):
22         return str(tuple(self))
23 
24     def __bytes__(self):
25         return (bytes([ord(self.typecode)]) + bytes(self._components))
26 
27     def __eq__(self, other):
28         return tuple(self) == tuple(other)
29 
30     def __abs__(self):
31         return math.sqrt(sum(x * x for x in self))
32 
33     def __bool__(self):
34         return bool(abs(self))
35 
36     @classmethod
37     def frombytes(cls, octets):
38         typecode = chr(octets[0])
39         memv = memoryview(octets[1:]).cast(typecode)
40         return cls(memv)
Vector

可見Vector新Vector

重載一元運算符

-  (__neg__)         一元取負運算符

+  (__pos__)         一元取正運算符

~  (__invert__)      對整數按位取反

     (__abs__)              取絕對值

實現它們:

    def __neg__(self):                     
        return Vector(-x for x in self)      #創建一個新的Vector實例,把self的每個分量都取反
    
    def __pos__(self):
        return Vector(self)                  #創建一個新的Vector實例,傳入self各個分量

__invert__不實現是因為,計算~v時python會拋出TypeError,且輸出詳細的錯誤信息,這符合預期。

!!注意:x和+x可能不相等,例如

import decimal
ctx = decimal.getcontext()      #獲取當前全局算術運算的上下文引用
ctx.prec = 40                   #把算術運算上下文精度設置為40
one_third = decimal.Decimal('1') / decimal.Decimal('3')  #計算1/3
print(one_third)
print(one_third == +one_third)  #
ctx.prec = 28                   #把精度降低為28,默認精度
print(one_third == +one_third)
print(+one_third)


0.3333333333333333333333333333333333333333
True 
False                                       #one_third == +one_third返回False
0.3333333333333333333333333333              #小數點后位是28位而不是40位

重載 + 運算符 

兩個向量加在一起生成一個新向量,如果兩個不同長度的Vector實例加一起呢,可以拋出錯誤,但最好是用0填充,那么可以:

    def __add__(self, other):
        pairs = itertools.zip_longest(self, other, fillvalue=0)
        return Vector(a + b for a, b in pairs)
        #pairs是一個生成器,生成(a, b)形式元組,a來自self,b來自other,0填充

嘗試調用:

if __name__ == '__main__':
    V1 = Vector([3, 4, 5])
    V2 = V1 + (2, 3, 4, 5)
    print(repr(V2))
    V3 = (2, 3, 4, 5) + V1
    print(repr(V3))

#結果
Vector([5.0, 7.0, 9.0, 5.0])
TypeError: can only concatenate tuple (not "Vector") to tuple

實現的加法可以處理任何數值可迭代對象,但是對調操作數加法就會失敗。實際上a + b調用的是a.__add__(b),而b + a自然是調用b.__add__(a)。而(2,3,4,5)顯然沒有實現這樣的加法,如何解決?

為了支持涉及不同類型的運算,python為中綴運算符提供了特殊的分派機制。對於表達式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__的反向版本。那么加法可以這樣寫:

    def __add__(self, other):
        pairs = itertools.zip_longest(self, other, fillvalue=0)
        return Vector(a + b for a, b in pairs)
    
    def __radd__(self, other):
        return self + other        #__radd__直接委托__add__

新問題是,如果操作類型是單個數或者‘helloworld’這樣的字符串拋出的錯誤不合理:

    V1 = Vector([3, 4, 5])
    V2 = V1 + 1
#TypeError: zip_longest argument #2 must support iteration

    V1 = Vector([3, 4, 5])
    V2 = V1 + 'abc'
#TypeError: unsupported operand type(s) for +: 'float' and 'str'

由於類型不兼容而導致運算符特殊方法無法返回有效的結果,那么應該返回NotImplemented,而不是TypeError。返回NotImplemented時,另一個操作數所屬的類型還有機會執行運算,即python會嘗試調用反向方法。

    def __add__(self, other):
        try:
            pairs = itertools.zip_longest(self, other, fillvalue=0)
            return Vector(a + b for a, b in pairs)
        except TypeError:
            return NotImplemented

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

發生TypeError時,解釋器嘗試調用反向運算符方法,如果操作數是不同的類型,對調之后可能反向運算符可以正確計算。

重載 * 運算符 

向量 * x,如果x是數值,就是向量每個分量乘以x。

    def __mul__(self, other):
        return Vector(n * other for n in self)
    
    def __rmul__(self, other):
        return self * other

問題是:提供不兼容的操作數就會出問題,other需要的參數是數字,傳入bool類型,或者fractions.Fraction實例等就會出問題。

 這里采用類型檢測,但是不硬編碼具體類型,而是檢查numbers.Real(因為不可能是復數)抽象基類,這個抽象基類涵蓋了所有可能可以參與這個運算的類型

    def __mul__(self, other):
        if isinstance(self, numbers.Real):
            return Vector(n * other for n in self)
        else:
            return NotImplemented

    def __rmul__(self, other):
        return self * other

中綴運算符見表:

重載比較運算符

比較運算符區別於+ * / 等:

(1) 正向反向調用使用的是同一系列方法,規則如表13-2.例如__gt__方法調用反向__lt__方法,並且把參數對調。

(2)對於==和!=來說,如果反向調用失敗,python會比較對象ID,而不拋出TypeError。

僅僅比較長度以及各個分量顯然是不合理的,因該加上類型檢查:

    def __eq__(self, other):
        if isinstance(other, Vector):
            return len(self) == len(other) and all(a == b for a, b in zip(self, other))
        else:
            return NotImplemented

對於!=運算符,從object繼承的__ne__方法后備行為滿足了需求:定義了__eq__方法且不返回NotImplemented時,__ne__會對__eq__返回的結果取反

(事實上可能x==y成立不代表x!=y不成立,有時需要定義__ne__方法)

 object繼承的__ne__方法即是下面代碼的C語言版本:

    def __ne__(self, other):
        eq_result = self == other
        if eq_result is NotImplemented:
            return NotImplemented
        else:
            return not eq_result

重載增量賦值運算符

 對於Vector類,它是不可變類型,是不能實現 += 這樣的就地方法的。

某可變的類型:

class B(A):

def __add__(self, other):
    if isinstance(other, A):
        return B(self.某屬性+other.某屬性)
    else:
        return NotImplemented

def __iadd__(self, other):
    if isinstance(other, A)
        other_iterable = other.某屬性
    else:
        try:
            other_iterable = iter(other)
        except TpyeError:
             #拋出xxx錯誤
        使用other_iterable更新self
        return self

 以上來自《流暢的python》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM