1. 程式人生 > >第047講:魔法方法:定製序列

第047講:魔法方法:定製序列

目錄

0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

測試題(筆試,不能上機哦~)

0. 你知道 Python 基於序列的三大容器類指的是什麼嗎?

1. Python 允許我們自己定製容器,如果你想要定製一個不可變的容器(像 String),你就不能定義什麼方法?

2. 如果希望定製的容器支援 reversed() 內建函式,那麼你應該定義什麼方法?

3. 既然是容器,必然要提供能夠查詢“容量”的方法,那麼請問需要定義什麼方法呢?

4. 通過定義哪些方法使得容器支援讀、寫和刪除的操作?

5. 為什麼小甲魚說“在 Python 中的協議就顯得不那麼正式”?

動動手(一定要自己動手試試哦~)

0. 根據課堂上的例子,定製一個列表,同樣要求記錄列表中每個元素被訪問的次數。


0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

常言道:“無規矩不成方圓”,講的是萬事萬物的發展都要在一定的規則下去執行,只有遵循一定的協議去做,事情才能夠按照正確的道路去發展。我們今天要談的是 定製容器,想要成功的實現容器的定製,我們要先談一談協議,那麼什麼是協議呢?

(1)協議是什麼?

協議(Protlcols)與其他程式語言中的介面很相似,它規定你在哪些地方必須定義哪些東西。然而,在Python中的協議就顯得不那麼正式了。事實上,Python中的協議更像是一種指南,一種建議。

例如,我們之前談到的鴨子型別(DuckTyping):當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。

Python就是這樣,它並不會嚴格要求你要怎樣去做,而是靠你自覺和經驗去把事情做得更好。

(2)定製容器

在Python中,序列型別(例如:列表,元組,字串)或者對映型別(例如:字典)都是屬於容器型別,它們都是裡面存放各式各樣的物件。那麼這一節課我們就來談定製容器。

我們需要知道的是有關定義容器的協議。這裡有兩種區分:

如果說你希望定製的容器是不可變的話(例如元組、字串),你只需要定義  __len__() 和 __getitem__() 方法。

如果說你希望定製的容器是可變的話,那麼除了 需要定義  __len__() 和 __getitem__() 方法之外,你還需要定義 __setitem__() 和 __delitem__() 兩個方法。

有關Python魔法方法的講解如果忘了的,可以看一下->Python魔法方法詳解

(3)練習

編寫一個不可改變的自定義列表,要求記錄列表中每個元素被訪問的次數。

引數是可變數量的 (*args),因為我們不知道使用者要傳入多少個數據,我們把使用者輸入的資料初始化為一個列表,self.values 就是一個列表,我們通過列表推導式的形式把資料存放到 self.values 這個列表中。另外,還需要記錄列表中每個元素被訪問的次數,我們立刻會想到字典,我們把每個元素在列表中的下標作為字典的鍵,然後值就是累計的訪問次數。我們定義 self.count 這個字典,初始化可以使用 fromkeys ,並把所有下標對應的Key所對應的值初始化為0。我們這是一個不可變的容器,所以需要定義  __len__() 和 __getitem__() 方法,  __len__() 就直接是返回 len(self.values) 的值,__getitem__() 中的 key 就是對應的下標,我們這裡是獲取key對應的值,所以需要返回self.values[key],另外,對應著訪問了它一次,所以對應的 self.count[key] 加1。

class CountList:
        def __init__(self, *args):
                self.values = [x for x in args]
                self.count = {}.fromkeys(range(len(self.values)), 0)

        def __len__(self):
                return len(self.values)

        def __getitem__(self, key):
                self.count[key] += 1
                return self.values[key]

驗證執行:

>>> c1 = CountList(1, 3, 5, 7, 9)
>>> c2 = CountList(2, 4, 6, 8, 10)
>>> c1.count
{0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
>>> c2.count
{0: 0, 1: 0, 2: 0, 3: 0, 4: 0}
>>> c1[1]
3
>>> c1.count
{0: 0, 1: 1, 2: 0, 3: 0, 4: 0}
>>> c1[2] + c2[2]
11
>>> c1.count
{0: 0, 1: 1, 2: 1, 3: 0, 4: 0}
>>> c2.count
{0: 0, 1: 0, 2: 1, 3: 0, 4: 0}

測試題(筆試,不能上機哦~)

0. 你知道 Python 基於序列的三大容器類指的是什麼嗎?

答:無疑是列表(List),元組(Tuple)和字串(String)啦。G

1. Python 允許我們自己定製容器,如果你想要定製一個不可變的容器(像 String),你就不能定義什麼方法?

答:如果你想要定製一個不可變的容器(像 String),你就不能定義像 __setitem__() 和 __delitem__() 這些會修改容器中的資料的方法。

2. 如果希望定製的容器支援 reversed() 內建函式,那麼你應該定義什麼方法?

答:應該定義 __reversed__() 方法,提供對內建函式 reversed() 的支援。

3. 既然是容器,必然要提供能夠查詢“容量”的方法,那麼請問需要定義什麼方法呢?

答:在 Python 中,我們通過 len() 內建函式來查詢容器的“容量”,所以容器應該定義 __len__() 方法。L

4. 通過定義哪些方法使得容器支援讀、寫和刪除的操作?

答:讀 —— __getitem__(),寫 —— __setitem__(),刪除 —— __delitem__(),"u

5. 為什麼小甲魚說“在 Python 中的協議就顯得不那麼正式”?

答:在 Python 中,協議更像是一種指南。這有點像我們之前在課後作業中提到的“鴨子型別”(忘了的朋友請戳:https://blog.csdn.net/qq_41556318/article/details/84640823) —— 當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。Python就是這樣,並不會嚴格地要求你一定要怎樣去做,而是讓你靠著自覺和經驗把事情做好!


動動手(一定要自己動手試試哦~)

0. 根據課堂上的例子,定製一個列表,同樣要求記錄列表中每個元素被訪問的次數。

這一次我們希望定製的列表功能更加全面一些,比如支援 append()、pop()、extend() 原生列表所擁有的方法。你應該如何修改呢?

要求1:實現獲取、設定和刪除一個元素的行為(刪除一個元素的時候對應的計數器也會被刪除)
要求2:增加 counter(index) 方法,返回 index 引數所指定的元素記錄的訪問次數
要求3:實現 append()、pop()、remove()、insert()、clear() 和 reverse() 方法(重寫這些方法的時候注意考慮計數器的對應改變)

今天只有一道動動手的題目,但在寫程式碼的時候要時刻考慮到你的列表增加了計數器功能,所以請務必要考慮周全再提交答案。

附課堂上的例子:

class CountList:
        def __init__(self, *args):
                self.values = [x for x in args]
                self.count = {}.fromkeys(range(len(self.values)), 0)

        def __len__(self):
                return len(self.values)

        def __getitem__(self, key):
                self.count[key] += 1
                return self.values[key]

程式碼清單:

class CountList(list):
    def __init__(self, *args):
        super().__init__(args)
        self.count = []
        for i in args:
            self.count.append(0)

    def __len__(self):
        return len(self.count)

    def __getitem__(self, key):
        self.count[key] += 1
        return super().__getitem__(key)

    def __setitem__(self, key, value):
        self.count[key] += 1
        super().__setitem__(key, value)

    def __delitem__(self, key):
        del self.count[key]
        super().__delitem__(key)

    def counter(self, key):
        return self.count[key]

    def append(self, value):
        self.count.append(0)
        super().append(value)

    def pop(self, key=-1):
        del self.count[key]
        return super().pop(key)

    def remove(self, value):
        key = super().index(value)
        del self.count[key]
        super().remove(value)

    def insert(self, key, value):
        self.count.insert(key, 0)
        super().insert(key, value)

    def clear(self):
        self.count.clear()
        super().clear()

    def reverse(self):
        self.count.reverse()
        super().reverse()