2018-12-31 更新聲明:切片系列文章本是分三篇寫成,現已合并成一篇。合并後,修正了一些嚴重的錯誤(如自定義序列切片的部分),還對行文結構與章節銜接做了大量改動。原系列的單篇就不删除了,畢竟也是有單獨成篇的作用。特此聲明,請閱讀改進版—— Python進階:全面解讀進階特性之切片!https://mp.weixin.qq.com/s/IRAjR-KHZBPEEkdiofseGQ
切片是 Python 中最迷人最強大最 Amazing 的語言特性(幾乎沒有之一),在《Python進階:切片的誤區與進階用法》中,我介紹了切片的基礎用法、進階用法以及一些使用誤區。這些内容都是基于原生的序列類型(如字元串、清單、元組......),那麼,我們是否可以定義自己的序列類型并讓它支援切片文法呢?更進一步,我們是否可以自定義其它對象(如字典)并讓它支援切片呢?
1、魔術方法: __getitem__()
__getitem__()
想要使自定義對象支援切片文法并不難,隻需要在定義類的時候給它實作魔術方法
__getitem__()
即可。是以,這裡就先介紹一下這個方法。
文法:
object.__getitem__(self, key)
官方文檔釋義:Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the
__getitem__()
method. If key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if key is missing (not in the container), KeyError should be raised.
概括翻譯一下:
__getitem__()
方法用于傳回參數 key 所對應的值,這個 key 可以是整型數值和切片對象,并且支援負數索引;如果 key 不是以上兩種類型,就會抛 TypeError;如果索引越界,會抛 IndexError ;如果定義的是映射類型,當 key 參數不是其對象的鍵值時,則會抛 KeyError 。
2、自定義序列實作切片功能
接下來,我們定義一個簡單的 MyList ,并給它加上切片功能。(PS:僅作示範,不保證其它功能的完備性)。
class MyList():
def __init__(self):
self.data = []
def append(self, item):
self.data.append(item)
def __iter__(self):
return self
def __getitem__(self, key):
print("key is : " + str(key))
return self.data[key]
l = MyList()
l.append("My")
l.append("name")
l.append("is")
l.append("Python貓")
print(l[3])
print(l[:2])
print(l['hi'])
### 輸出結果:
key is : 3
Python貓
key is : slice(None, 2, None)
['My', 'name']
key is : hi
Traceback (most recent call last):
...
TypeError: list indices must be integers or slices, not str
從輸出結果來看,自定義的 MyList 既支援按索引查找,也支援切片操作,這正是我們的目的。
特别需要說明的是,此例中的
__getitem__()
方法會根據不同的參數類型而實作不同的功能(取索引位值或切片值),也會妥當地處理異常,是以并不需要我們再去寫繁瑣的處理邏輯。網上有不少學習資料完全是在誤人子弟,它們會教你區分參數的不同類型,然後寫一大段代碼來實作索引查找和切片文法,簡直是畫蛇添足。下面的就是一個代表性的錯誤示例:
###略去其它代碼####
def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice): # 如果index是個切片類型,則構造新執行個體
return cls(self._components[index])
elif isinstance(index, numbers.Integral): # 如果index是個數,則直接傳回
return self._components[index]
else:
msg = "{cls.__name__} indices must be integers"
raise TypeError(msg.format(cls=cls))
3、自定義字典實作切片功能
切片是序列類型的特性,是以在上例中,我們不需要寫切片的具體實作邏輯。但是,對于其它非序列類型的自定義對象,就得自己實作切片邏輯。以自定義字典為例(PS:僅作示範,不保證其它功能的完備性):
class MyDict():
def __init__(self):
self.data = {}
def __len__(self):
return len(self.data)
def append(self, item):
self.data[len(self)] = item
def __getitem__(self, key):
if isinstance(key, int):
return self.data[key]
if isinstance(key, slice):
slicedkeys = list(self.data.keys())[key]
return {k: self.data[k] for k in slicedkeys}
else:
raise TypeError
d = MyDict()
d.append("My")
d.append("name")
d.append("is")
d.append("Python貓")
print(d[2])
print(d[:2])
print(d[-4:-2])
print(d['hi'])
### 輸出結果:
is
{0: 'My', 1: 'name'}
{0: 'My', 1: 'name'}
Traceback (most recent call last):
...
TypeError
上例的關鍵點在于将字典的鍵值取出,并對鍵值的清單做切片處理,其妙處在于,不用擔心索引越界和負數索引,将字典切片轉換成了字典鍵值的切片,最終實作目的。
4、小結
最後小結一下:本文介紹了
__getitem__()
魔術方法,并用于實作自定義對象(以清單類型和字典類型為例)的切片功能,希望對你有所幫助。
參考閱讀:
Python進階:切片的誤區與進階用法
官方文檔getitem用法:http://t.cn/EbzoZyp
Python切片指派源碼分析:http://t.cn/EbzSaoZ
PS:本公衆号(Python貓)已開通讀者交流群,詳情請通過菜單欄中的“交流群”來了解。
-----------------
本文原創并首發于微信公衆号【Python貓】,背景回複“愛學習”,免費獲得20+本精選電子書。