2018-12-31 更新聲明:切片系列文章本是分三篇寫成,現已合併成一篇。合併後,修正了一些嚴重的錯誤(如自定義序列切片的部分),還對行文結構與章節銜接作了大量改動。原系列的單篇就不刪除了,畢竟也是有單獨成篇的做用。特此聲明,請閱讀改進版—— Python進階:全面解讀高級特性之切片!https://mp.weixin.qq.com/s/IRAjR-KHZBPEEkdiofseGQpython
切片是 Python 中最迷人最強大最 Amazing 的語言特性(幾乎沒有之一),在《Python進階:切片的誤區與高級用法》中,我介紹了切片的基礎用法、高級用法以及一些使用誤區。這些內容都是基於原生的序列類型(如字符串、列表、元組......),那麼,咱們是否能夠定義本身的序列類型並讓它支持切片語法呢?更進一步,咱們是否能夠自定義其它對象(如字典)並讓它支持切片呢?app
__getitem__()
想要使自定義對象支持切片語法並不難,只須要在定義類的時候給它實現魔術方法 __getitem__()
便可。因此,這裏就先介紹一下這個方法。ide
語法: 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 。lua
接下來,咱們定義一個簡單的 MyList ,並給它加上切片功能。(PS:僅做演示,不保證其它功能的完備性)。.net
<code class="language-python">class MyList(): def __init__(self): self.data = [] def append(self, item): self.data.append(item) 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__()
方法會根據不一樣的參數類型而實現不一樣的功能(取索引位值或切片值),也會穩當地處理異常,因此並不須要咱們再去寫繁瑣的處理邏輯。網上有很多學習資料徹底是在誤人子弟,它們會教你區分參數的不一樣類型,而後寫一大段代碼來實現索引查找和切片語法,簡直是多此一舉。下面的就是一個表明性的錯誤示例:code
###略去其它代碼#### 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))
切片是序列類型的特性,因此在上例中,咱們不須要寫切片的具體實現邏輯。可是,對於其它非序列類型的自定義對象,就得本身實現切片邏輯。以自定義字典爲例(PS:僅做演示,不保證其它功能的完備性):component
<code class="language-python">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
上例的關鍵點在於將字典的鍵值取出,並對鍵值的列表作切片處理,其妙處在於,不用擔憂索引越界和負數索引,將字典切片轉換成了字典鍵值的切片,最終實現目的。
最後小結一下:本文介紹了__getitem__()
魔術方法,並用於實現自定義對象(以列表類型和字典類型爲例)的切片功能,但願對你有所幫助。
參考閱讀:
官方文檔getitem用法:http://t.cn/EbzoZyp
Python切片賦值源碼分析:http://t.cn/EbzSaoZ