本文主要內容 序列類型分類: (1)容器序列、扁平序列 (2)可變序列、不可變序列 列表推導式 生成器表達式 元組拆包 切片 排序(list.sort方法和sorted函數) bisect 文中代碼均放在github上:https://github.com/ampeeg/cnblogs/tree/m ...
本文主要內容
序列類型分類:
(1)容器序列、扁平序列
(2)可變序列、不可變序列
列表推導式
生成器表達式
元組拆包
切片
排序(list.sort方法和sorted函數)
bisect
文中代碼均放在github上:https://github.com/ampeeg/cnblogs/tree/master/python高級
序列類型分類
所謂序列,即元素有序排列,python標準庫用C實現了豐富的序列類型,按照序列中是否可存放不同類型的數據分為"容器序列"和"扁平序列"。
容器序列可以存放統統類型的數據,而扁平序列只能存放一種類型
容器序列:list、tuple、collections.deque
扁平序列:str、bytes、bytearray、memoryview、array.array
按照是否能修改的標準序列又可分為"可變序列"和"不可變序列":
可變序列:list、bytearrary、array.arrary、collections.deque和memoryview
不可變序列:tuple、str和bytes
由於可變序列繼承自不可變序列,所以可變序列繼承的方法也較多,下麵看看它們包含的方法:
方法名 | 不可變序列 | 可變序列 |
__contains__ | 有 | 有 |
__iter__ | 有 | 有 |
__len__ | 有 | 有 |
__getitem__ | 有 | 有 |
__reversed__ | 有 | 有 |
index | 有 | 有 |
count | 有 | 有 |
__setitem__ | 有 | |
__delitem__ | 有 | |
insert | 有 | |
append | 有 | |
reverse | 有 | |
extend | 有 | |
pop | 有 | |
remove | 有 | |
__iadd__ | 有 |
我們以tuple和list類型為例,對比源代碼中的方法,可以明顯發現list的方法多於tuple:
列表推導式
# 列表推導式生成的是列表,會占用系統記憶體 # 基本語法 list_1 = [x for x in range(1, 20)] list_2 = [x ** 2 for x in range(1, 20)] print(list_1) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] print(list_2) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361] # 笛卡爾積型的列表推導式 list_3 = [(x, y) for x in range(1, 3) # 1,2 for y in range(7, 10)] # 7、8、9 # 該表達式會先將1分別和7、8、9組合,然後再拿2和7、8、9組合,共6對 print(list_3) # [(1, 7), (1, 8), (1, 9), (2, 7), (2, 8), (2, 9)] list_4 = [x+y for x in range(1, 3) for y in range(7, 10)] print(list_4) # [8, 9, 10, 9, 10, 11] # 還可以添加if語句 l = [1, 3, 4, 33, 45, 36, 422, 34, 67, 23, -4, -7, -345, 46, -6, -45, 32, -8, -4, 67, -4] list_5 = [x for x in l if x > 0] # 只取出大於0的生成列表 print(list_5) # [1, 3, 4, 33, 45, 36, 422, 34, 67, 23, 46, 32, 67] |
生成器表達式
# 雖然列表推導式可以用來初始化元組、數組或其他序列類型,但是列表推導式會直接生成列表,占用記憶體 # 而生成器遵守了迭代器協議,可以逐個產出元素,而不是先建立一個完整的列表 # 生成器表達式直接將推導式的方括弧換成圓括弧即可 g = (x for x in range(1, 10000)) print(g) # <generator object <genexpr> at 0x105c0efc0> :生成器對象 from collections import Iterable, Iterator if isinstance(g, Iterable): print("iterable") # 輸出iterable: 說明生成器g是可迭代的 if isinstance(g, Iterator): print("iterator") # 輸出iterator:說明生成器g是迭代器 |
下麵我們來對比一下列表推導式和生成器的效率
# 比較列表推導式和生成器 import time start_time = time.time() l = [x for x in range(1000000)] print(time.time() - start_time) # 0.1361069679260254 start_time = time.time() g = (x for x in range(1000000)) print(time.time() - start_time) # 1.1205673217773438e-05 # 可見,生成器遠快於推導式 |
元組拆包
# 我們經常這樣給兩個變數同時賦值 a, b = 1, 2 print(a, b) # 1 2 # 還可以這樣 a, b = [1, 2] print(a, b) # 1 2 # 也可以這樣 a, b = (1, 2) print(a, b) # 1 2 # 甚至可以這樣 a, b = "ab" print(a, b) # a b ''' 像以上這樣連續的賦值方式,右邊可以使用逗號隔開;也可以是序列。 當拆包賦值的是序列時,python解釋器會先找該序列中的__iter__方法,如果該方法不存在,則尋找__getitem__方法。 接下來說其他用法 ''' # 賦值後優雅地交換兩個變數 a, b = (1, 2) a, b = b, a print(a, b) # 2 1 # 使用*號來處理多餘的數據 a, b, *s = [1, 2, 3, 4, 5, 6, 7, 8, 9] print(a, b, s) # 1 2 [3, 4, 5, 6, 7, 8, 9] # 這樣從第三個元素開始的所有值都賦給了s a, b, *s = (1, 2, 3, 4, 5, 6, 7, 8, 9) print(a, b, s) # 1 2 [3, 4, 5, 6, 7, 8, 9] # 註意,本來是元組,賦之後的s變成了列表. 如果s為空的話也會返回空列表 *s, a, b = (1, 2, 3, 4, 5, 6, 7, 8, 9) print(s, a, b) # [1, 2, 3, 4, 5, 6, 7] 8 9 # *s也可以放在前面 a, *s, b = (1, 2, 3, 4, 5, 6, 7, 8, 9) print(a, s, b) # 1 [2, 3, 4, 5, 6, 7, 8] 9 # *s也可以放在中間 # 嵌套元組拆包 a, b, (c, d) = (1, 2, (3, 4)) print(a, b, c, d) # 1 2 3 4 # 只要按照右邊的形式就可賦值 a, b, *c = (1, 2, (3, 4)) print(a, b, c) # 1 2 [(3, 4)] |
1 ################################ 2 # 3 # 以下的例子用以說明拆包賦值時,解釋器會按照__iter__、__getitem__的順序調用類中的方法 4 # 5 ################################ 6 class Foo: 7 def __init__(self, s): 8 self.s = s 9 10 def __iter__(self): 11 print("iter") 12 return iter(self.s) 13 14 def __getitem__(self, item): 15 return self.s[item] 16 17 if __name__ == "__main__": 18 foo = Foo("sdfafasfasf") 19 a, b, *s = foo 20 print(a, b)拆包賦值的內部實現
之前我們通過源碼已經對比過list和tuple類中的方法和屬性,下麵列出《流暢的python》整理的列表和元組的方法及屬性:
表 列表或元組的方法和屬性
列 表 | 元 組 | 說 明 | |
s.__add__(s2) | · | · | s1 + s2 , 拼接 |
s.__iadd__(s2) | · | s1 += s2,就地拼接 | |
s.append(e) | · | 在尾部添加一個新元素 | |
s.clear() | · | 刪除所有元素 | |
s.__contains__(e) | · | · | s是否包含e |
s.copy() | · | 列表的淺複製 | |
s.count(e) | · | · | e在s中出現的次數 |
s.__delitem__(p) | · | 把位於p的元素刪除 | |
s.extend(it) | · | 把可迭代對象it追加給s | |
s.__getitem__(p) | · | · | s[p],獲取位置p的元素 |
s.__getnewargs__() | · | 在pickle中支持更加優化的序列化 | |
s.index(e) | · | · | 在s中找到元素e第一次出現的位置 |
x.insert(p,e) | · | 在位置p之前拆入e | |
s.__iter__() | · | · | 獲取s的迭代器 |
s.__len__() | · | · | len(s),長度 |
s.__mul__(n) | · | · | s * n,n個s的重覆拼接 |
s.__imul__(n) | · | s *= n,就地城府拼接 | |
s.__rmul__(n) | · | · | n * s,反向拼接* |
s.pop([p]) | · | 刪除最後或者是位於p的元素,並返回它的值 | |
s.remove(e) | · | 刪除s中第一次出現的e | |
s.reverse() | · | 就地把s的元素倒序排列 | |
s.__reversed__() | · | 返回s的倒序迭代器 | |
s.__setitem__(p,e) | · | s[p]=e,把元素e放在位置p,替代已經在那個位置的元素 | |
s.sort([key], [reverse]) | · | 就地對s中的元素進行排序,可選的參數有key和是否倒序reverse |
說明:以上元組中不加黑點的不代表一定不能這樣使用,只是其作用和列表不同(說明裡面有解釋)。例如兩個元組a和b進行增量賦值a+=b也是可以的,只是這個操作不是就地拼接,而是生成了新的元組。
切片
''' 在python中,內置的序列類型都支持切片操作,切片操作的用法十分簡單: list[start: stop: step] , 其中不包括區間範圍內最後一個(事實上這是python的風格,一般不包含區間最後一個) python裡面能使用切片操作是因為實現了__getitem__方法,切片時會給該方法傳遞slice(start: stop: step) 參數 ''' if __name__ == "__main__": # 基本操作 l = [1, 2, 3, 4, 5, 6, 7, 8, 9] print(l[2:]) # 第3個元素到最後 :[3, 4, 5, 6, 7, 8, 9] print(l[:3]) # 第一個元素到最後 :[1, 2, 3] s = "abcdefghijklmn" print(s[2::2]) # 從第三個字母開始,隔一個字母取一個 : cegikm print(s[::-1]) # 倒序排列 : nmlkjihgfedcba print(s[::-2]) # 倒序隔一個取一個 nljhfdb print(s[-2::-2]) # 倒序第二隔開始,隔一個取一個 # 利用切片賦值 l[2:5] = [20, 30] print(l) # [1, 2, 20, 30, 6, 7, 8, 9] try: l[2:5] = 40 # 報錯:TypeError: can only assign an iterable # 利用切片賦值時傳入的必須是可迭代對象 except Exception as e: print(e) # can only assign an iterable l[2:5] = (40,) print(l) # [1, 2, 40, 7, 8, 9] l[2:3] = "sajfljls" # 字元串屬於序列,也可以迭代 print(l) # [1, 2, 's', 'a', 'j', 'f', 'l', 'j', 'l', 's', 7, 8, 9] |
排序(list.sort方法和sorted函數)
''' list.sort方法和sorted內置函數都有排序的功能,區別如下 list.sort是就地排序列表,不會把原列表複製一份。該方法返回None,以提醒不會新建一個列表。 sorted函數會新建一個列表作為返回值,這個函數可以接受任何可迭代對象,甚至包括不可變序列或生成器,最後返回的總是列表。 list.sort和sorted都有兩個參數: reverse:預設為False,設定為True以降序排列 key:一個只有一個參數的函數,這個函數會作用於序列的每一個元素上,然後以該函數的結果作為關鍵字排序 ''' if __name__ == "__main__": # 1、list.sort就地排序,而sorted返回列表 l = [x for x in range(10, 0, -1)] # 初始化一個列表:[10, 9, 8, 7, 6, 5, 4, 3, 2, 1] print(id(l), l) # l最初的地址:4536449800 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] l.sort() print(id(l), l) # 排序後的地址:4536449800 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # l前後的的地址沒變,說明是就地排序 l = [x for x in range(10, 0, -1)] # 初始化一個列表:[10, 9, 8, 7, 6, 5, 4, 3, 2, 1] print(id(l), l) # l最初的地址:4415318984 [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] l = sorted(l) print(id(l), l) # 排序後的地址:4415318792 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 2、sorted可以接受任何可迭代對象 l = (x for x in range(10, 0, -1)) print(type(l)) # 迭代器 <class 'generator'> print(sorted(l)) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] s = "qwertyuiopasdfghjklzxcvbnm" # 字元串序列 print(sorted(s)) # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'] s = (1, 3, 2, 456, 345, 12, 2, 5, 78, 34) # 不可變元組 print(sorted(s)) # [1, 2, 2, 3, 5, 12, 34, 78, 345, 456] # 3、reverse參數 s = "qwertyuiopasdfghjklzxcvbnm" print(sorted(s, reverse=True)) # ['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'] # 4、key參數 s = "QwERTYuioPaSdfGHjKLzXcvbnm" print(sorted(s)) # ['E', 'G', 'H', 'K', 'L', 'P', 'Q', 'R', 'S', 'T', 'X', 'Y', 'a', 'b', 'c', 'd', 'f', 'i', 'j', 'm', 'n', 'o', 'u', 'v', 'w', 'z'] print(sorted(s, key=str.lower)) # 忽略大小寫 ['a', 'b', 'c', 'd', 'E', 'f', 'G', 'H', 'i', 'j', 'K', 'L', 'm', 'n', 'o', 'P', 'Q', 'R', 'S', 'T', 'u', 'v', 'w', 'X', 'Y', 'z'] print(sorted(s, key=str.upper)) # 也是忽略大小寫 |
########################## # # 以下自定義一個類也可使用sorted函數 # ########################## class Obj: def __init__(self): self.s = [x for x in range(10, 0, -1)] def __getitem__(self, item): print("getitem") return self.s[item] def __repr__(self): return str(self.s) def __iter__(self): return iter(self.s) obj = Obj() print(obj) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] # 添加getitem後可以使用sorted函數 (實驗時請註視掉getitem方法) print(sorted(obj)) # 列印10次getitem , [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 添加iter方法 print(sorted(obj)) # 此時解釋器會先調用iter方法,不會再使用getitem方法 # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]使自定義類也可使用sorted函數調用 |
bisect
''' bisect模塊主要用來管理有順序的序列 bisect模塊包含的主要函數是bisect和insort,兩個函數都使用二叉樹方法搜索 1、bisect(haystack, needle) haystack必須是一個有序的序列,該函數搜索needle在haystack中的位置,該位置使得將needle插入後haystack仍然升序 查找到位置後可用haystack.insert()插入 2、insort(seq, item) 把item插入到seq中,並能保持seq的升序 ''' # 本人認為《流暢的python》中的對該模塊介紹的例子比較經典,故引用之 # 1、關於bisect.bisect的示例 import bisect import sys HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30] NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31] ROW_FMT = '{0:2d} @ {1:2d} {2}{0:<2d}' def demo(bisect_fn): for needle in reversed(NEEDLES): position = bisect_fn(HAYSTACK, needle) offset = position * ' |' print(ROW_FMT.format(needle, position, offset)) if __name__ == '__main__': if sys.argv[-1] == 'left': bisect_fn = bisect.bisect_left else: bisect_fn = bisect.bisect print('DEMO:', bisect_fn.__name__) print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK)) demo(bisect_fn) |
# 2、關於bisect.insort函數 import bisect import random SIZE = 7 random.seed(1729) my_list = [] for i in range(SIZE): new_item = random.randrange(SIZE*2) bisect.insort(my_list, new_item) print('%2d ->' % new_item, my_list) '''輸出: 10 -> [10] 0 -> [0, 10] 6 -> [0, 6, 10] 8 -> [0, 6, 8, 10] 7 -> [0, 6, 7, 8, 10] 2 -> [0, 2, 6, 7, 8, 10] 10 -> [0, 2, 6, 7, 8, 10, 10] ''' # 另,insort函數也有insort_left,背後使用的是bisect_left |