Python3面向對象-運算符重載

目錄

1:運算符重載介紹

2:Python3中常見運算符重載方法

3:運算符重載方法示例

3.1:索引和分片:__getitem__和__setitem__

3.2:返回數值:__index__   (__index__不是索引)

3.3:可迭代對象:__iter__,__next__

3.3.1:單遍迭代

3.3.2:多遍迭代

3.3.3:__iter__ 加 yield 實現多遍迭代

3.4:屬性訪問:__getattr__,__getattribute__和__setattr__

3.4.1 __getattr__,__getattribute__

3.4.2__setattr__

3.4.3 __getattr__ 和 __setattr__總結

3.5:調用表達式:__call__

3.6:字符串顯示:__str__ 和 __repr__

3.7:比較運算



1:運算符重載介紹

運算符重載,就是在某個類的方法中,攔截其內置的操作(比如:+,-,*,/,比較,屬性訪問,等等),使其實例的行爲接近內置類型。

當類的實例出現在內置操作中時(比如:兩個實例相加  +),Python會自動調用你的方法(比如:你重載的__add__方法),並且你的方法的返回值會作爲相應操作的結果。

Python3中的運算符重載:
運算符重載讓類攔截常規的Python操作。
類可以重載所有Python表達式運算符。
類也可以重載打印,函數調用,屬性訪問等內置運算。
重載是通過在一個類中提供特殊名稱的方法來實現的。

2:Python3中常見運算符重載方法

方法名 實現功能 觸發調用的形式
__new__ 創建 在__init__之前的對象創建
__init__ 構造函數 對象創建:X = Class(args)
__del__ 析構函數 X對象回收
__add__ '+'運算符 X+Y
X += Y  (如果存在__iadd__,則使用重載後的__iadd__)
__or__ '|'運算符(按位或) X | Y
X |= Y
 
__repr__,__str__ 打印,轉換 print(X),repr(X),str(X)
__call__ 函數調用 X(*args,**kargs)
__getattr__ 屬性訪問 X.undefined
__setattr__ 屬性賦值 X.any=value
__delattr__ 屬性刪除 del X.any
__getattribute__ 屬性訪問 X.any
__getitem__ 索引,
分片,
迭代

X[key]

X[i:j]

沒有重載__iter__方法的for循環和其他迭代操作

__setitem__ 索引賦值
分片賦值

X[key] = value

X[i:j] = iterable

__delitem__ 索引刪除
分片刪除

del X[key]

del X[i:j]

__len__ 長度 len(X)
沒有重載__bool__方法的真值測試
__bool__ 布爾測試 bool(X)
真值測試
__lt__,__gt__,
__le__,__ge__,
__eq__,__ne__
比較 X < Y,X > Y
X <= Y,X >= Y
X == Y,X != Y
 
__radd__ 右則 "+" 操作 Other + X
__iadd__ 原位置"+="操作 X += Y (如果沒有重載該方法,則使用__add__)
__iter__,__next__ 迭代上下文

I = iter(X),next(I)
for循環

沒有重載__contains__方法的in操作

所有的推導表達式

map(F,X)

其他

__contains__ 成員關係測試 item in X(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.attr

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,即fib
for 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 = 200
print('4:' ,t.__dict__)

"""
1: {'age': 100}
0
2:******************************
in __getattr__: bb
None
3:******************************
100
4: {'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 100
1:******************************
{}
2:******************************
in __getattr__: age
None
3:******************************
in __setattr__: age 200
in __setattr__: name ixusy
4:******************************
{'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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章