1. 程式人生 > >Python——運算子過載(1)

Python——運算子過載(1)

運算子過載

關鍵概念:

1.運算子過載讓類攔截常規的Python運算。
2.類可過載所有的Python表示式運算子。
3.類也可過載列印、函式呼叫、屬性點號運算等內建運算。
4.過載使類例項的行為像內建型別。
5.過載是通過特殊名稱的類方法來實現的。

運算子過載只是意味著在類方法中攔截內建的操作——當類的例項出現在內建操作中,Python自動呼叫你的方法,並且你的方法的返回值變成了相應操作的結果。

=======================================================================

建構函式和表示式:__init__和__sub__

看一個簡單的過載例子。下述Number類提供了一個方法來攔截例項的建構函式(__init__),此外還有一個方法捕捉減法表示式(__sub__)。這種特殊的方法是鉤子,可與內建運算繫結。

>>> class Number:
	def __init__(self,start):
		self.data = start
	def __sub__(self,other):
		return Number(self.data - other)

	
>>> X = Number(5)
>>> X.data
5
>>> Y = X - 2
>>> Y.data
3
=======================================================================

常見的運算子過載方法

在類中,對內建物件(例如,整數和列表)所能做的事,幾乎都有相應的特殊名稱的過載方法。下表列出其中一些最常用的過載方法。

方法 過載 呼叫
__init__ 建構函式 物件建立:X = Class(args)
__del__ 解構函式 X物件收回
__add__ 運算子+ 如果沒有_iadd_,X+Y,X+=Y
__or__ 運算子|(位OR) 如果沒有_ior_,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] = sequence
__delitem__ 索引和分片刪除 del X[key],del X[i:j]
__len__ 長度 len(X),如果沒有__bool__,真值測試
__bool__ 布林測試 bool(X),真測試
__lt__,__gt__, 特定的比較 X < Y,X > Y
__le__,__ge__, X<=Y,X >= Y
__eq__,__ne__ X == Y,X != Y
__radd__ 右側加法 Other+X
__iadd__ 實地(增強的)加法 X += Y (or else __add__)
__iter__,__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__ 描述符屬性 X.attr,X.attr = value,del X.attr
__new__ 建立 在__init__之前建立物件

所有過載方法的名稱前後都有兩個下劃線字元,以便把同類中定義的變數名區別開來。

運算子過載方法都是可選的——如果沒有編寫或繼承一個方法,你的類直接不支援這些運算,並且試圖使用它們會引發一個異常。

多數過載方法只用在需要物件行為表現的就像內建型別一樣的高階程式中。然而,__init__建構函式常出現在絕大多數類中。

下面介紹一些運算子過載的用法示例。

=======================================================================

索引和分片:__getitem__和__setitem__

如果在類中定義了的話,則對於例項的索引運算,會自動呼叫__getitem__,把X作為第一個引數傳遞,並且方括號內的索引值傳給第二個引數。例如,下面的類將返回索引值的平方。

>>> class Indexer:
	def __getitem__(self,index):
		return index**2

	
>>> X = Indexer()
>>> X[2]
4

>>> for i in range(5):
	print(X[i],end = ' ')

	
0 1 4 9 16 
------------------------------------------------------------------------------------------------------------------

攔截分片

有趣的是,除了索引,對於分片表示式也呼叫__getitem__。正式地講,內建型別以同樣的方式處理分片。例如,下面是一個內建列表上工作的分片,使用了上邊界和下邊界以及一個stride。(分片的知識可以回顧這裡

>>> L = [5,6,7,8,9]
>>> L[2:4]
[7, 8]
>>> L[1:]
[6, 7, 8, 9]
>>> L[:-1]
[5, 6, 7, 8]
>>> L[::2]
[5, 7, 9]
實際上,分片邊界繫結到了一個分片物件中(即slice物件),並且傳遞給索引的列表實現。實際上,我們總是可以手動地傳遞一個分片物件——分片語法主要是用一個分片物件進行索引的語法糖:
>>> L[slice(2,4)]
[7, 8]
>>> L[slice(1,None)]
[6, 7, 8, 9]
>>> L[slice(None,-1)]
[5, 6, 7, 8]
>>> L[slice(None,None,2)]
[5, 7, 9]
__getitem__既針對基本索引呼叫,又針對分片呼叫。我們前面的類沒有處理分片,但是,如下類將會處理分片。當針對基本索引呼叫時,引數像前面一樣是一個整數。
>>> class Indexer:
	data = [5,6,7,8,9]
	def __getitem__(self,index):
		print('getitem:',index)
		return self.data[index]

	
>>> X = Indexer()
>>> X[0]
getitem: 0
5
>>> X[1]
getitem: 1
6
>>> X[-1]
getitem: -1
9
然而,當針對分片呼叫時,方法接收一個分片物件,它將一個新的索引表示式直接傳遞給巢狀的列表索引:
>>> X[2:4]
getitem: slice(2, 4, None)
[7, 8]
>>> X[1:]
getitem: slice(1, None, None)
[6, 7, 8, 9]
>>> X[:-1]
getitem: slice(None, -1, None)
[5, 6, 7, 8]
>>> X[::2]
getitem: slice(None, None, 2)
[5, 7, 9]
如果使用的話,__setitem__索引賦值方法類似地攔截索引和分片賦值——它為後者接收了一個分片物件,它可能以同樣的方式傳遞到另一個索引賦值中:
def __setitem__(self,index,value):
	...
	self.data[index] = value
------------------------------------------------------------------------------------------------------------------
索引迭代:__getitem__

這是一個很有用的技巧。for語句的作用是從0到更大的索引值,重複對序列進行【索引運算】,直到檢測到超出邊界的異常。因此,__getitem__也可以是Python中一種過載迭代的方式。如果定義了這個方法,for迴圈每次迴圈時都會呼叫類的__getitem__方法,並持續搭配有更高的偏移值。如下:
>>> class stepper:
	def __getitem__(self,i):
		return self.data[i]

	
>>> X = stepper()
>>> X.data = 'Spam'
>>> X[1]
'p'
>>> for item in X:
	print(item,end = ' ')

	
S p a m 
我們知道,任何支援for迴圈的類也會自動支援Python所有的迭代環境,比如成員關係測試in、列表解析、內建函式map、列表和元祖賦值運算以及型別構造方法等,如下示例:
>>> 'p' in X
True
>>> [c for c in X]
['S', 'p', 'a', 'm']
>>> list(map(str.upper,X))
['S', 'P', 'A', 'M']
>>> a,b,c,d = X
>>> a,c,d
('S', 'a', 'm')
>>> list(X),tuple(X),''.join(X)
(['S', 'p', 'a', 'm'], ('S', 'p', 'a', 'm'), 'Spam')
>>> X
<__main__.stepper object at 0x0376A6B0>
在實際應用中,這個技巧可以用於建立提供序列介面的物件,並新增邏輯到內建的序列的型別運算。
=======================================================================

迭代器物件:__iter__和__next__

儘管上述介紹的__getitem__技術有效,但它只是迭代的一種退而求其次的方法。一般Python中的迭代環境都會先嚐試__iter__方法,再嘗試__getitem__方法。

迭代環境是通過呼叫內建函式iter去嘗試尋找__iter__方法來實現的,而這種方法應該返回一個迭代器物件。如果已經提供了,Python就會重複呼叫這個迭代器物件的next方法,直到發生StopIteration異常。如果沒有找到這類__iter__方法,Python會改用__getitem__機制,就像之前那樣通過偏移量重複索引,直到引發IndexError異常。
------------------------------------------------------------------------------------------------------------------

使用者定義的迭代器

在__iter__機制中,類就是通過之前介紹的【迭代器協議】來實現使用者定義的迭代器的。下述示例定義了使用者定義的迭代器類來生成平方值。

>>> 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,end = ' ')

	
1 4 9 16 25 
在這個例子中,迭代器物件就是例項self,因為next方法是這個類的一部分。
和__getitem__不同的是,__iter__只迴圈一次,而不是迴圈多次。每次新的迴圈,都得建立一個新的迭代器物件,如下:
>>> X = Squares(1,5)
>>> [n for n in X]
[1, 4, 9, 16, 25]
>>> [n for n in X]
[]
>>> [n for n in Squares(1,5)]
[1, 4, 9, 16, 25]
------------------------------------------------------------------------------------------------------------------

有多個迭代器的物件

之前提到過迭代器物件可以定義成一個獨立的類,有其自己的狀態資訊,從而能夠支援相同資料的多個迭代。看下例:

>>> s = 'ace'
>>> for x in s:
	for y in s:
		print(x+y,end=' ')

		
aa ac ae ca cc ce ea ec ee
在這裡,外層迴圈呼叫iter從字串中取得迭代器,而每個巢狀的迴圈也做相同的事來獲得獨立的迭代器。

之前介紹過,生成器表示式,以及map和zip這樣的內建函式,都證明是單迭代物件;相反,range內建函式和其他的內建型別(如列表),支援獨立位置的多個活躍迭代器。

當我們用類編寫自己定義的迭代器的時候,由我們來決定是支援一個單個的或是多個活躍的迭代器。要達到多個迭代器的效果,__iter__只需替迭代器定義新的狀態物件,而不是返回self.

例如,下面定義了一個迭代器類,迭代時,跳過下一個元素。因為迭代器物件會在每次迭代時都重新建立,所以能夠支援多個處於啟用狀態下的迴圈。
>>> class SkipIterator:
	def __init__(self,wrapped):
		self.wrapped = wrapped
		self.offset = 0
	def __next__(self):
		if self.offset >= len(self.wrapped):
			raise StopIteration
		else:
			item = self.wrapped[self.offset]
			self.offset += 2
			return item

		
>>> class SkipObject:
	def __init__(self,wrapped):
		self.wrapped = wrapped
	def __iter__(self):
		return SkipIterator(self.wrapped)

	
>>> alpha = 'abcdef'
>>> skipper = SkipObject(alpha)
>>> I = iter(skipper)
>>> print(next(I),next(I),next(I))
a c e
>>> for x in skipper:
	for y in skipper:
		print(x+y,end = ' ')

		
aa ac ae ca cc ce ea ec ee
這個例子工作起來就像是對內建字串進行巢狀迴圈一樣,因為每個迴圈都會獲得獨立的迭代器物件來記錄自己的狀資訊,所以每個啟用狀態下的迴圈都有自己在字串中的位置。

=======================================================================

成員關係:__contains__、__iter__和__getitem__

在迭代領域,類通常把in成員關係運算符實現為一個迭代,使用__iter__方法或__getitem__方法。要支援更加特定的成員關係,類可能編寫一個__contains__方法——當出現的時候,該方法優先於__iter__方法,__iter__方法優先於__getitem__方法。

__contains__方法應該把成員關係定義為對一個對映應用鍵,以及用於序列的搜尋。

考慮下面的類,它編寫了3個方法和測試成員關係以及應用於一個例項的各種迭代環境。呼叫的時候,其方法會打印出跟蹤訊息。

class Iters:
    def __init__(self,value):
	    self.data = value
    def __getitem__(self,i):
    	print('get[%s]:'%i,end = '')
    	return self.data[i]
    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,x):
    	print('contains:',end='')
    	return x in self.data

if __name__ == '__main__':
    X = Iters([1,2,3,4,5])
    print(3 in X)
    for i in X:
        print(i,end='')
    print()
    print([i**2 for i in X])
    print(list(map(bin,X)))

    I = iter(X)
    while 1:
        try:
            print(next(I),end = ' @ ')
        except StopIteration:
            break
這段指令碼執行的時候,其輸出如下所示:
contains:True
iter=> next:1next:2next:3next:4next:5next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:next:next:next:next:next:['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:
可以看到,特定的__contains__攔截成員關係,通用的__iter__捕獲其他的迭代環境以致__next__被重複呼叫,而__getitem__不會被呼叫。

但是,要觀察如果註釋掉__contains__方法後代碼的輸出發生了什麼變化——成員關係現在路由到了通用的__iter__:
iter=> next:next:next:True
iter=> next:1next:2next:3next:4next:5next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:next:next:next:next:next:['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:
但是,如果__contains__和__iter__都註釋掉的話,其輸出如下——索引__getitem__替代方法會被呼叫,針對成員關係和其他迭代環境使用連續較高的索引:
get[0]:get[1]:get[2]:True
get[0]:1get[1]:2get[2]:3get[3]:4get[4]:5get[5]:
get[0]:get[1]:get[2]:get[3]:get[4]:get[5]:[1, 4, 9, 16, 25]
get[0]:get[1]:get[2]:get[3]:get[4]:get[5]:['0b1', '0b10', '0b11', '0b100', '0b101']
get[0]:1 @ get[1]:2 @ get[2]:3 @ get[3]:4 @ get[4]:5 @ get[5]:
正如我們看到的,__getitem__方法更加通用。

相關推薦

Python——運算子過載(1)

運算子過載 關鍵概念: 1.運算子過載讓類攔截常規的Python運算。 2.類可過載所有的Python表示式運算子。 3.類也可過載列印、函式呼叫、屬性點號運算等內建運算。 4.過載使類例項的行為像內建型別。 5.過載是通過特殊名稱的類方法來實現的。 運算子過載只是意味著在

Python——運算子過載(2)

繼續介紹運算子過載的知識。 ======================================================================== 屬性引用:__getattr__和__setattr__ __getattr__方法是攔截屬性點號

Python 運算子過載

常見的運算子過載方法 方法 過載 呼叫 __init__ 建構函式 物件建立: X = Class(args) __del__ 解構函式 X物件收回 __add__ 運算子+ 如果沒有_i

python運算子過載

剛剛學習python,對於運算子過載,也不是很懂,個人理解如下: 類的專有方法(未截完): ------------------------------------------------------------------------------------------

C++中的運算子過載-1

Opencv學堂 http://mp.weixin.qq.com/s?__biz=MzA4MDExMDEyMw==&mid=100000109&idx=1&sn=7540b49e869c3e27f87c84f6f3dfe9a8&chksm

Python ==運算子過載

在Python中is、==是兩個運算子,對物件進行比較,分別對ID,TYPE,Value進行比較。 "is"比較ID,TYPE,Value三維,而"=="僅比較value。 實驗發現其實is,==僅僅是比較一些簡單的基礎變數。 class Test(object):

PythonPython運算子過載(簡版)

__add__(self, other): + __sub__(self, other): - __mul__(self, other): * __matmul__(self, other): @ __truediv__(self, other): / __floordiv_

c++運算子過載1

1、c++絕大多數運算子允許過載,不能過載的運算子只有幾個: . 成員訪問運算子 . * 成員指標訪問運算子 :: 作用域運算子 Sizeof 長度運算子 ?

Python 看書的一些記錄 運算子過載

1.類和模組有什麼關係?   (1)類是模組的一部分,是模組物件的屬性。   (2)類和模組都是名稱空間,但是類是對於語法的。模組是對於檔案的   (3)類支援多個例項,但是模組被匯入時只有一個。 2.什麼是抽象超類(抽象基類)?   抽象超類就是指類的部分行為需要由其子類提供 class

Python學習-第1課(變數,字串,運算子,迴圈)

學習前準備~ 環境安裝:   Anaconda3+PyCharm python文件   python概述: 一、Hello world及註釋   二、變數 1. 命名規則: 變數命名可以包含數字,大小寫字母,下劃線 數字不可以在開頭 一般在pytho

(D20)Python-異常高階,運算子過載

異常(高階) 異常相關的語句: try-except try-finally raise 觸發異常,發生錯誤通知 assert 根據條件觸發AssertionError型別的錯誤通知 with 語句 with語句 語法:

探索Scala(1)-- 運算子過載

Scala語言運算子過載完全是語法層面的小把戲,本文記錄我對Scala語言運算子過載的一些理解。 方法呼叫語法糖 呼叫方法時,Scala允許省略點號和圓括號,如下面程式碼所示: 把運算子對映成單詞 對於Scala來說,運算子和普通的方法沒什麼兩樣。比如下面這個類就過載了

python day18 異常(高階) 一元運算子過載 、 關係運算符的過載

目錄: 異常(高階) 一元運算子過載 、 關係運算符的過載 、 with語句 語法: with 表示式1 [as 變數名1], 表示式2 [as 變數名2], ... 作用: 用於對資源訪問的場合,確保使用過程中不管是否發生

《C++第九周實驗報告3-1》----接第8周任務3,定義分數類中運算子過載,實現分數的輸入輸出

/* (程式頭部註釋開始) * 程式的版權和版本宣告部分 * Copyright (c) 2011, 煙臺大學計算機學院學生 * All rights reserved. * 檔名稱: CFraction.cpp *

Java 深究字串String類(1)之運算子"+"過載

一.不可改變String String物件是不可改變的, 檢視JDK文件,發現String類中每一個看似修改String的方法,實際上都建立了一個新的String物件,而最初的String物件則絲毫未動. java傳遞引數的時候,傳遞的是

成員函式和友元函式 完成二元和一元運算子過載(進階1

二元運算子過載: 全域性函式: #include <iostream> using namespace std; class Complex{//複數類 private: int

python學習筆記22(運算子過載

運算子過載 不同型別的物件之間的運算子會有不同的解釋,如: print(1 + 2) print("1" + "2") #不同的型別用加法會有不同的解釋 利用運算子過載實現兩個字串數字相加 class Person(object): def __init__(sel

POJ C++程式設計 程式設計題#1 程式設計作業—運算子過載

程式設計題 #2 來源: POJ (Coursera宣告:在POJ上完成的習題將不會計入Coursera的最後成績。) 注意: 總時間限制: 1000ms 記憶體限制: 65536kB 描述 下面的MyInt類只有一個成員變數。MyInt類內部的部分程式碼被隱藏了。假設下面

第八週(專案三1)——分數類中的運算子過載.

/* *煙臺大學計算機學院學生 *All right reserved. *檔名稱*煙臺大學計算機學院學生 *All right reserved. *檔名稱:分數類中的運算子過載 *作者:王洪海 *完成日期:2013年4月20日 *版本號:v1.0 *對任務及求解方法的描

Python運算子過載 __iter__()和 __next__()

   Python語言提供了運算子過載功能,增強了語言的靈活性,這一點與C++有點類似又有些不同。鑑於它的特殊性,今天就來討論一下Python運算子過載。       Python語言本身提供了很多魔法方法,它的運算子過載就是通過重寫這些Python內建魔法方法實現的。