Python基礎之正確重載運算符

導語:本文章記錄了本人在學習Python基礎之面向對象篇的重點知識及我的心得,打算入門Python的朋友們能夠來一塊兒學習並交流。

本文重點:python

一、掌握運算符重載的定義和做用,以及Python對其的內部限制;
二、掌握一元運算符重載設計思路;
三、理解中綴運算符重載過程當中鴨子類型和白鵝類型思想的運用並掌握。

1、運算符重載基礎

運算符重載:對已有的運算符進行從新定義,賦予其另外一種功能,以適應不一樣的數據類型。
重載的做用:令用戶定義的對象可以使用中綴運算符(如 + 和 | )或一元運算符(如 - 和 ~ )等運算符。
爲了作好靈活性、可用性和安全性方面的平衡,Python對運算符重載施加了一些限制:安全

  • 不能重載內置類型的運算符
  • 能新建運算符,只能重載現有運算符
  • 某些運算符不能重載,如is、and、or和not(不過位運算符&、| 和 ~能夠)

2、一元運算符

一、常見的一元運算符

  • -(__neg__),一元取負算術運算符。例:若 x 是 -2,則 -x==2。
  • +(__pos__),一元取正算術運算符。一般x==+x。
  • ~(__invert__),對整數按位取反,~x== -(x+1)。例:若 x 是 -2,則 ~x==1。

另外,Python語言參考手冊將內置的abs()函數列爲一元運算符,它對應的特殊方法是__abs__。函數

二、重載一元運算符

重載一元運算符只需實現相應的特殊方法,這些特殊方法只有self一個參數。
重載應遵循運算符的一個基本規則:始終返回一個新對象
即,不能修改self,要建立並返回合適類型的新實例。學習

下面咱們以第10章的多維向量類爲例重載一元運算符:spa

import math
class Vector:
#排版須要省略中間代碼
    def __abs__(self):
        return math.sqrt(sum(x*x for x in self))
    
    def __neg__(self):
        return Vector(-x for x in self)
    
    def __pos__(self):
        return Vector(self)

    def __invert__(self):
        return Vector(-x-1 for x in self)

三、x和+x不相等的狀況

  • 算術運算上下文的精度變化可能致使 x 不等於 +x

圖片描述
Python 3.4 爲 Decimal 算術運算設定的默認精度是28,這裏由於+x使用上下文的精度致使相等性判斷返回False。設計

  • counter實例不含零值和負值計算器

圖片描述
經過上面的實例可以看到counter實例ct通過零值和負值的賦值以後,再通過+x運算後發現ct實例中的非負數對象均消失了。事實上一元運算符 + 等同於加上一個空 Counter。當Counter相加時,Python解釋器從實用性角度出發會把負值和零值的計數從結果中剔除。code

3、中綴運算符

一、重載加法__add__

如今咱們仍以第10章的多維向量爲例進行中綴運算符加號「+」的重載。
重載加法的目標分析orm

  • 當多維向量類是操做數時,多維向量應支持與同類向量的加法
  • 同時多維向量類還應支持與可迭代對象的加法
  • 此外當可迭代對象是操做數的時候,多維對象應具有__radd__如此來調用多維向量類中的__add__方法

重載加法的流程圖設計:
設計的重點在於採用鴨子類型思想。當多維向量類與非數值類相加時,多維向量類沒法處理異類加法運算能夠將加法運算交給右操做數的類處理。由於右操做數存在能夠處理這種異類加法的可能。
圖片描述對象

重載加法的代碼實現:繼承

from itertools import zip_longest
class Vector:
#排版須要省略中間代碼
    def __add__(self, other):
        try:
            return Vector(a+b for a,b in zip_longest(self,other,fillvalue=0))
        except TypeError:
            return NotImplemented
    def __radd__(self, other):
        return self+other

二、重載乘法__mul__
重載加法的目標分析

  • 當多維向量類是操做數時,多維向量應支持與同類向量的乘法
  • 同時多維向量類還應支持與可迭代對象的加法
  • 此外當可迭代對象是操做數的時候,多維對象應具有__rmul__如此來調用多維向量類中的__mul__方法

注意:咱們對多維向量重載的乘法是針對數論中的實數類型進行運算,此時能夠採用白鵝類型顯式檢查對象的抽象基類是否爲numbers.Real,代碼實現以下:

import numbers 
class Vector:
#排版須要省略中間代碼
    def __mul__(self, other):
        if isinstance(other,numbers.Real):
                return Vector(x*other for x in self)
        else:
            return NotImplemented
    def __rmul__(self, other):
        return self*other

Tips:通常來講只有當處理與self不一樣類型的操做數時,須要建立反向方法處理。不然沒有必要建立反向方法。

4、比較運算符

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

  • 正向和反向調用使用的是同一系列方法。這方面的規則以下表所示。例如,對 == 來講,正向和反向調用都是 _eq_ 方法,只是把參數對調了;而正向的 _gt_ 方法調用的是反向的 __lt__方法, 並把參數對調。
  • 對 == 和 != 來講,若是反向調用失敗,Python 會比較對象的 ID,而不拋出 TypeError。

圖片描述

一、重載等號__eq__

如今咱們仍以第10章的多維向量爲例進行中綴運算符等號「=」的重載。
重載等號的返回爲True的條件

  • 等號兩端對象爲同類對象
  • 等號兩端對象中的每一個元素都必須對應相等

注意:若Vector處理等號不爲True,應該返回NotImplemented交由Python處理。若是反向調用返回 NotImplemented,Python 會使用後備機制比較對象的 ID,做最後一搏。

重載等號的代碼實現以下:

class Vector:
#排版須要省略中間代碼
    def __eq__(self, other):
        if isinstance(other,Vector):
            return len(self)==len(other) and all(x==y for x,y in zip(self,other))
        else:
            return NotImplemented

二、瞭解object中__ne__的實現

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

5、增量賦值運算符

一、重載加等於__iadd__

如今咱們仍以第10章的多維向量爲例進行中綴運算符加等於「+=」的重載。
重載加等於設計要求

  • 加等於右側的對象與左側的Vector類是同類對象或可迭代對象
  • 不然拋出TypeError,顯示沒法進行加等於計算

下面以BingoCage的子類AddableBingoCage爲例實現__iadd__,你們沒必要在乎這個子類,重點在於理解__iadd__實現的思路:

import itertools 
from tombola import Tombola
from bingo import BingoCage
class AddableBingoCage(BingoCage): 
    def __add__(self, other):
        if isinstance(other, Tombola): 
            return AddableBingoCage(self.inspect() + other.inspect()) 
            #self.inspect()繼承自BingoCage,返回當前元素組成的有序元組。
        else:
            return NotImplemented
    def __iadd__(self, other):
        if isinstance(other, Tombola):
            other_iterable = other.inspect() 
        else:
            try:
                other_iterable = iter(other)
            except TypeError: 
                self_cls = type(self).__name__
                msg = "right operand in += must be {!r} or an iterable"
                raise TypeError(msg.format(self_cls))
        self.load(other_iterable) 
        return self
相關文章
相關標籤/搜索