Python過載運算子實現自定義序列

2020-07-16 10:05:08
Python 可過載的運算子中,有如下幾個和序列操作相關的特殊方法:
  • __len__(self):該方法的返回值決定序列中元素的個數。
  • __getitem__(self, key):該方法獲取指定索引對應的元素。該方法的 key 應該是整數值或 slice 物件,否則該方法會引發 KeyError 異常。
  • __contains__(self, item):該方法判斷序列是否包含指定元素。
  • __setitem__(self, key, value):該方法設定指定索引對應的元素。該方法的 key 應該是整數值或 slice 物件,否則該方法會引發 KeyError 異常。
  • __delitem__(self, key):該方法刪除指定索引對應的元素。

在此基礎上,如果根據特定的需求對這些方法進行過載,即可實現自定義一個序列類。

如果程式要實現不可變序列(程式只能獲取序列中的元素,不能修改),只要實現上面前 3 個方法就行;如果程式要實現可變序列(程式既能獲取序列中的元素,也可修改),則需要實現上面 5 個方法。

下面程式將會實現一個字串序列,在該字串序列中預設每個字串的長度都是 3,該序列的元素按 AAA、 AAB、 AAC…… 這種格式排列:
def check_key (key):
    '''
    該函數將會負責檢查序列的索引,該索引必須是整數值,否則引發TypeError
    且程式要求索引必須為非負整數,否則引發IndexError
    '''
    if not isinstance(key, int): raise TypeError('索引值必須是整數')
    if key < 0: raise IndexError('索引值必須是非負整數')
    if key >= 26 ** 3: raise IndexError('索引值不能超過%d' % 26 ** 3)  
class StringSeq:
    def __init__(self):
        # 用於儲存被修改的資料
        self.__changed = {}
        # 用於儲存已刪除元素的索引
        self.__deleted = []
    def __len__(self):
        return 26 ** 3
    def __getitem__(self, key):
        '''
        根據索引獲取序列中元素
        '''
        check_key(key)
        # 如果在self.__changed中找到已經修改後的資料
        if key in self.__changed :
            return self.__changed[key]
        # 如果key在self.__deleted中,說明該元素已被刪除
        if key in self.__deleted :
            return None
        # 否則根據計算規則返回序列元素
        three = key // (26 * 26)
        two = ( key - three * 26 * 26) // 26
        one = key % 26
        return chr(65 + three) + chr(65 + two) + chr(65 + one)
    def __setitem__(self, key, value):
        '''
        根據索引修改序列中元素
        '''
        check_key(key)
        # 將修改的元素以key-value對的形式儲存在__changed中
        self.__changed[key] = value
    def __delitem__(self, key):
        '''
        根據索引刪除序列中元素
        '''
        check_key(key)
        # 如果__deleted列表中沒有包含被刪除key,新增被刪除的key
        if key not in self.__deleted : self.__deleted.append(key)
        # 如果__changed中包含被刪除key,刪除它
        if key in self.__changed : del self.__changed[key]
# 建立序列
sq = StringSeq()
# 獲取序列的長度,實際上就是返回__len__()方法的返回值
print(len(sq))
print(sq[26*26])
# 列印沒修改之後的sq[1]
print(sq[1]) # 'AAB'
# 修改sq[1]元素
sq[1] = 'fkit'
# 列印修改之後的sq[1]
print(sq[1]) # 'fkit'
# 刪除sq[1]
del sq[1]
print(sq[1]) # None
# 再次對sq[1]賦值
sq[1] = 'crazyit'
print(sq[1]) # crazyit
上面程式實現了一個 StringSeq 類,並為該類實現了一個 __len__()、 __getitem__()、 __setitem__() 和 __delitem__() 方法,其中 __len__() 方法返回該序列包含的元素個數,__getitem__() 方法根據索引返回元素,__setitem__() 方法根據索引修改元素的值,而 __delitem__() 方法則用於根據索引刪除元素。

需要注意的是,該序列本身並不儲存序列元素,它會根據索引動態計算序列元素,因此在使用時需要儲存使用 __changed 和 __deleted 分別儲存被修改和被刪除的元素。
 
在定義了字串序列之後, 接下來程式建立了序列物件,並呼叫序列方法測試該工具類。執行該程式,可以看到如下輸出結果:

17576
BAA
AAB
fkit
None
crazyit

從上面的輸出結果來看,程式中序列的第二個元素 sq[1] 恰好為 'AAB',程式既可對序列元素賦值,也可刪除、修改序列元素,這完全是一個功能完備的序列。