1. 程式人生 > >PythonI/O進階學習筆記_4.自定義序列類(序列基類繼承關係/可切片物件/推導式)

PythonI/O進階學習筆記_4.自定義序列類(序列基類繼承關係/可切片物件/推導式)

 

前言:

本文程式碼基於python3

Content:

1.python中的序列類分類

2. python序列中abc基類繼承關係

3. 由list的extend等方法來看序列類的一些特定方法

4. list這種序列類的一大特點:切片。和如何實現可切片物件。到如何自定義一個序列類。

5. biset維護排序序列

6. 什麼時候使用list

7.列表推導式、生成器表示式、字典推導式

 

1.python中的序列類分類

a.什麼是python的序列類?

之前提到的魔法函式這種時候就很重要啦。滿足序列類相關的協議就稱為python裡的序列類。python內建的序列類有dict、tuple、list等。

而我們自定義序列類的話,由於魔法函式的存在。序列的相關魔法方法允許我們自己建立的類擁有序列的特性,讓其使用起來就像 python 的內建序列。

 

b.python按分類來看有哪些序列類?

-  容器序列:list,tuple,deque(可以放任意的型別的容器)

-  扁平序列:str,bytes,bytearray,array.array(可以使用 for迴圈遍歷的)

-  可變序列:list,deque,bytearray,array

-  不可變:str,tuple,bytes

ps.這裡推薦一本書 fluent python 

  Python標準庫提供了大量使用C來實現的序列型別,

  從序列中的元素型別是否一致作為標準,包括容器序列(Container sequences,包括list、tuple、collections.deque等)和固定序列(Flat sequences,包括str、bytes、bytearray、memoryview、array.array)等。

  ps.容器序列中實際存放的元素是對其他任意物件的引用,而固定序列中存放是真正的是元素值,因此所有的元素必須是相同型別,並且只能是Python基本型別(字元、位元組、數字等)的資料。

  如果從序列中的元素是否能夠被修改的標準來看,Python的序列型別又分為可變序列(Mutable sequences,包括list、bytearray、array.array、collections.deque、memoryview等)和不可變序列(Immutable sequences,包括tuple、str、bytes等)

 

c.序列常見操作

索引(index) 分片(slicing) 序列相加(拼接) 乘法(重複) 成員資格(in) 長度(len) max() min() 可以迭代
sorted enumerate zip filter map

 

2. python序列中的abc繼承關係

在python的collection模組中,有abc模組,和容器相關的抽象基類和資料結構都在其中。

那麼abc模組中,具體有哪些類呢?

 

其中,Sequence就是可變序列的方法集合的抽象基類,MutableSequence是集合了不可變序列的方法和協議的抽象基類。

這些抽象類之間的關係:

a.可變序列(MutableSequence)從不可變序列(Sequence)那裡繼承了一些方法.

b.Sequence繼承了collection,collection又繼承了Sized、Container、Iterable

c.python的內建序型別並沒有直接繼承這些基類,但是這些基類定義了某種特性序列的方法和協議,瞭解這些基類間的繼承關係能很好的幫助我們瞭解python的內建序列型別。

綜上可得圖:

 

 

3. 由list的extend等方法來看序列類的一些特定方法呼叫

對list序列進行新增操作一般有幾種方法?

+、+=、extend、append

 

這裡有幾個好玩的地方:

a.+和+=的區別

可以看到+=的物件可以是tuple,而+不可以。

實際上+=是呼叫了一個魔法函式 __iadd__實現的。使用+=的時候,實際上還是呼叫了extend方法。

extend傳遞的引數型別是可迭代型別,用for迴圈,它會遍歷可迭代的型別,一個個加到列表裡。

 

 

 

b.extend、append的區別

上面已經貼出了extend的實現,現在看append的實現:

很明顯的一點,一個直接用insert把物件插入,一個遍歷物件加入。

 

 

 

 

4. list這種序列類的一大特點:切片。和如何實現可切片物件。到如何自定義一個序列類。

a.python的切片的一些用法:

alist = [3,4,5,6,7,9,11,13,15,17]
print(alist[::]) # 返回包含原列表中所有元素的新列表
print(alist[::-1]) # 返回原列表的逆序排列
print(alist[::2]) # 返回原列表的偶數位資料
print(alist[1::2]) # 獲取奇數位置的資料
print(alist[3:6]) # 指定切片的開始和結束位置
print(alist[0:100]) # 切片位置大於列表長度時,從列表尾部截斷
print(alist[100:]) # 切片開始位置大於列表長度時,返回空列表
alist[len(alist):]=[9] # 在列表尾部增加元素
print(alist)
alist[:0] = [1,2] # 前面的0省略了,相當於是alist[0:0] = [1,2]
print(alist)      # 在列表的頭部增加元素
alist[3:3] =[4] # 在列表中間插入元素
print(alist)
alist[:3] = [1,2] # 相當於是alist[0:3] = [1,2] 替換列表元素
alist[3:] = [4,5,6] # 替換元素,結果按照兩邊的最短元素來決定.
print(alist)
alist[:3] = []  # 刪除列表中前三個元素
del alist[:3] # 切片元素連續
del alist[::2] # 隔一個刪除一個,切片元素不連續

 

b.自己實現一個可切片的序列類(包括可切片、可新增等內建序列型別有的操作)

自定義序列的相關魔法方法允許我們自己建立的類擁有序列的特性,讓其使用起來就像 python 的內建序列(dict,tuple,list,string等)。

如果要實現這個功能,就要遵循 python 的相關的協議。所謂的協議就是一些約定內容。例如,如果要將一個類要實現迭代,可以實現__iter__() 或者 __getitem__()其中一個方法

例子:自定義一個可以被切片的Group類:

import numbers


class Group:
    # 支援切片操作
    def __init__(self, group_name, company_name, staffs):
        self.group_name = group_name
        self.company_name = company_name
        self.staffs = staffs

    def __reversed__(self):
        self.staffs.reverse()

    # 因為object[] 和 object[::]都會調動這個方法
    def __getitem__(self, item):
        # 取到class
        cls = type(self)
        #判斷傳遞進來的是slice型別還是int型別,返回不同的型別和操作,
        if isinstance(item, slice):
            return cls(group_name=self.group_name, company_name=self.company_name,
                       staffs=self.staffs[item])
        if isinstance(item, numbers.Integral):
            return self.staffs[item]

    def __iter__(self):
        return iter(self.staffs)

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

    def __str__(self):
        return '組員有:{}'.format(self.staffs)

    def __contains__(self, item):
        if item in self.staffs:
            return True
        else:
            return False


staffs = ['tangrong1', '123', '456', '789']
group = Group('A', 'TR', staffs=staffs)
sub_group = group[:2]
print(group)
print(sub_group)

if 'A' in group:  # 這裡會呼叫__contains__魔法函式
    print('yes')

for item in group:
    print(item)

reversed(group) # 實際上是呼叫了__reversed__魔法函式
print(group)

輸出正常。

一些相關的魔法函式:

  • __len__(self)  返回容器的長度。可變和不可變容器都要實現它,這是協議的一部分。

  • __getitem__(self, key)   定義當某一項被訪問時,使用self[key]所產生的行為。這也是可變容器和不可變容器協議的一部分。如果鍵的型別錯誤將產生TypeError;如果key沒有合適的值則產生KeyError。
  • __setitem__(self, key, value)  定義當一個條目被賦值時,使用self[key] = value所產生的行為。這也是可變容器協議的一部分。而且,在相應的情形下也會產生KeyError和TypeError。
  • __delitem__(self, key)  定義當某一項被刪除時所產生的行為。(例如del self[key])。這是可變容器協議的一部分。當你使用一個無效的鍵時必須丟擲適當的異常。

 

  • __iter__(self)  返回一個迭代器,尤其是當內建的iter()方法被呼叫的時候,以及當使用for x in container:方式進行迴圈的時候。  迭代器要求實現next方法(python3.x中改為__next__),並且每次呼叫這個next方法的時候都能獲得下一個元素,元素用盡時觸發 StopIteration 異常。          而其實 for 迴圈的本質就是先呼叫物件的__iter__方法,再不斷重複呼叫__iter__方法返回的物件的 next 方法,觸發 StopIteration 異常時停止,並內部處理了這個異常,所以我們看不到異常的丟擲。    這種關係就好像介面一樣。
ps:

可迭代物件:物件實現了一個__iter__方法,這個方法負責返回一個迭代器。

迭代器:內部實現了next(python3.x為__next__)方法,真正負責迭代的實現。當迭代器內的元素用盡之後,任何的進一步呼叫都之後觸發 StopIteration 異常,所以迭代器需要一個__iter__方法來返回自身。
所以大多數的迭代器本身就是可迭代物件。這使兩者的差距進一步減少。 但是兩者還是不同的,如果一個函式要求一個可迭代物件(iterable),而你傳的迭代器(iterator)並沒有實現__iter__方法,那麼可能會出現錯誤。 不過一般會在一個類裡同時實現這兩種方法(即是可迭代物件又是迭代器),此時__iter__方法只要返回self就足夠的了。當然也可以返回其它迭代器。

 

  • __reversed__(self)  實現當reversed()被呼叫時的行為。應該返回序列反轉後的版本。僅當序列是有序的時候實現它,例如列表或者元組。

 

 

  • __contains__(self, item)  定義了呼叫in和not in來測試成員是否存在的時候所產生的行為。這個不是協議要求的內容,但是你可以根據自己的要求實現它。當__contains__沒有被定義的時候,Python會迭代這個序列,並且當找到需要的值時會返回True。

 

 

  • __missing__(self, key)  其在dict的子類中被使用。它定義了當一個不存在字典中的鍵被訪問時所產生的行為。(例如,如果我有一個字典d,當"george"不是字典中的key時,使用了d["george"],此時d.__missing__("george")將會被呼叫)。

 

 

5. biset維護排序序列

a.biset模組幹嘛的。模組裡的insort幹嘛的

bisect是針對序列型別來的,維持一個升序的序列,用二分查詢來維持一個可排序的序列。 insort: 插入 bisect right是預設方式   b.用法:

 

 

 

6. 什麼時候不應該使用list而用array

list和array的區別:

list是一個容器,裝任何型別的資料。而array只能裝指定型別的資料。

list記憶體空間不連續,array連續。

 

例子:用布隆過濾器的時候 就用了array。這時候array效能高list很多 

 

7.列表推導式、生成器表示式、字典推導式

# 列表推導式
# 1.提取出1-20之間的奇數
odd_list = []
for i in range(21):
    if i % 2 == 1:
        odd_list.append(i)
print(odd_list)

# 使用列表推導式
odd_list = [x for x in range(21) if x % 2 == 1]
print(odd_list)


# 列表推導式的格式
# [on True for x in iteralbe 條件表示式(過濾)]

# 邏輯複雜的情況
def handle_item(item):
    return item * item


odd_list = [handle_item(x) for x in range(21) if x % 2 == 1]
print(odd_list)
# 列表表示式的前面可以是一個函式,也可以是一個函式,但是不能是匿名函式

# 生成器表示式,將列表推導式的[]改成(),就變成了生成器表示式
gen = (x for x in range(21) if x % 2 == 1)
print(gen)  # <generator object <genexpr> at 0x000001CF1B01C8E0>
print(type(gen))  # <class 'generator'>
for item in gen:
    print(item)

# 字典推導式,顛倒key和value
my_dict = {'bob1': 22, 'bob3': 23, 'bob4': 5}

reversed_dict = {value: key for key, value in my_dict.items()}
print(reversed_dict)

# 集合推導式 set
# 如何將一個字典的key全部放到一個集合當中.
my_set = {key for key in my_dict.keys()}
# 也可以使用
my_set = set(my_dict.keys())
print(type(my_set))
print(my_set)

&n