[toc]html
第二章開始介紹了列表這種數據結構,這個在python是常常用到的結構python
列表的推導,將一個字符串編程一個列表,有下面的2種方法。
其中第二種方法更簡潔。可讀性也比第一種要好。編程
str = 'abc' string = [] # 第一種方法 for s in str: print(string.append(s)) # 第二種方法 ret = [s for s in str] print(ret)
用這種for…in的方法來推導列表,有個好處就是不會有變量泄露也就是越界的問題
列表的推導還有一種方式,稱爲生成器表達式。
表達式都差很少,不過是方括號編程了圓括號而已數組
生成器的好處是什麼呢?
數據結構
列表推導是首先生成一個組合的列表,這會佔用到內存。
而生成式則是在每個for循環運行時才生成一個組合,這樣就不會預先佔用內存
生成器表達式不會一次將整個列表加載到內存之中,而是生成一個生成器對象(Generator objector),因此一次只加載一個列表元素
舉個例子來講明:app
有20000個數組的列表。分別用列表推導法和生成式表達法來進行遍歷。
並用memory_profiler
來監控代碼佔用的內存dom
列表推導法例子:性能
# 若是系統沒有這個模塊就執行pip install memory_profiler進行安裝 from memory_profiler import profile @profile def fun_try(): test = [] for i in range(20000): test.append(i) for num in [t for t in test]: print(num) fun_try()
這個代碼運行結果是:網站
Line # Mem usage Increment Line Contents ================================================ 5 39.9 MiB 39.9 MiB @profile 6 def fun_try(): 7 39.9 MiB 0.0 MiB test = [] 8 9 40.8 MiB 0.0 MiB for i in range(20000): 10 40.8 MiB 0.1 MiB test.append(i) 11 12 41.2 MiB 0.2 MiB for num in [t for t in test]: 13 41.2 MiB 0.0 MiB print(num)
生成式例子:ui
# 若是系統沒有這個模塊就執行pip install memory_profiler進行安裝 from memory_profiler import profile @profile def fun_try(): test = [] for i in range(20000): test.append(i) for num in (t for t in test): print(num) fun_try()
這個代碼運行結果是:
Line # Mem usage Increment Line Contents ================================================ 5 40.1 MiB 40.1 MiB @profile 6 def fun_try(): 7 40.1 MiB 0.0 MiB test = [] 8 9 41.1 MiB 0.0 MiB for i in range(20000): 10 41.1 MiB 0.1 MiB test.append(i) 11 12 41.1 MiB 0.0 MiB for num in (t for t in test): 13 41.1 MiB 0.0 MiB print(num)
結論:
`經過這兩個結果能夠看到列表推導法增長了0.2MB的內存
除非特殊的緣由,應該常常在代碼中使用生成器表達式。
但除非是面對很是大的列表,不然是不會看出明顯區別的。
`
下面介紹下元組。說到元組,第一個反應就應該是不可變列表。
但做者同時還介紹了元組的不少其餘特性。
首先來看下元組的拆包。
#元組拆包 t = (20, 8) a, b = t print(a, b)
若是在進行拆包的同時,並非對全部的元組數據都感興趣。
_佔位符就能幫助處理這種狀況
import os _, filename = os.path.split("/home/jian/prj/demo.txt") print(_, filename) #/home/jian/prj demo.txt
上面元組拆包的時候是對可迭代對象進行遍歷,而後一一賦值到變量。
可是若是想經過給每一個元組元素的命名來訪問,則需用到命名元組namdtuple
# collections.namedtuple構建一個帶有字段名的元組和一個有名字的類 from collections import namedtuple, OrderedDict City = namedtuple('City', 'name country population') tokyo = City('Tokyo', 'JP', '123') tokyo_data = ('Tokyo', 'JP', '123') print(tokyo) # City(name='Tokyo', country='JP', population='123') print(tokyo.name, tokyo.country, tokyo.population) # Tokyo JP 123 print(City._fields) # ('name', 'country', 'population') tokyo = City._make(tokyo_data) print(tokyo) # City(name='Tokyo', country='JP', population='123') OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')]) print(tokyo._asdict()) # OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')])
*符號用來處理剩下的元素
a, b, *rest = range(5) print(a, b, rest) # 0 1 [2, 3, 4] a, *b, rest = range(5) print(a, b, rest) # 0 [1,2,3] 4
增量賦值:
增量運算符+,*等實際上是調用__iadd__/__add__/__mul__
方法。
對於可變對象來講,+調用的是__iadd__
,對於不可變對象來講對象調用的是__add__
。
二者有什麼區別呢。
先看下面的例子
str = [1, 2, 3] str1 = (1, 2, 3) print("str:%d" % id(str)) print("str1:%d" % id(str1)) str += str str1 += str1 print("str:%d" % id(str)) print("str1:%d" % id(str1))
獲得的結果以下:
str:2256630301640 str1:2256630239736 str:2256630301640 str1:2256630606152
str是列表,str1是元組,列表是可變對象,元組是不可變對象。
在進行加法運算後,str的id沒有改變,所以仍是以前的對象,可是str1的id卻發生了改變,不是以前的對象了。
這種的差異在於__iadd__
的方法相似調用a.extend(b)的方式,是在原有的對象上進行擴展操做。
可是__add__
的方式相似於a=a+b。
首先a+b獲得一個新的的對象,而後複製給a,所以變量和以前的對象沒有任何聯繫。
而是被關聯到一個新的對象。一樣的乘法__imul__/__mul__
也是相似的道理
列表組成的列表:
board = [['_'] * 3 for i in range(3)] print(board) board[1][2] = 'x' print(board)
運行結果:
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] [['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']]
輸出三個列表的ID
board = [['_'] * 3 for i in range(3)] print(board) board[1][2] = 'x' print(board) # 輸出3個列表的ID print(id(board[0])) print(id(board[1])) print(id(board[2]))
結果是分別屬於不一樣的ID:
3221578302664 3221578302536 3221578302600
咱們再來看下另一種用法。下面的代碼對一個包含3個列表的列表進行*3的操做。
board = [['_'] * 3] * 3 print(board) board[1][2] = 'x' print(board) print(id(board[0])) print(id(board[1])) print(id(board[2]))
運行結果是:
發現id都同樣。說明了所有指向的是同一個對象。
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] [['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']] 1195278432328 1195278432328 1195278432328
若是對不可變對象中的可變對象進行賦值會產生什麼後果,好比下面的這段代碼
t = (1, 2, [30, 40]) t[2] += [50, 60] print(t)
運行結果直接報錯:
TypeError: 'tuple' object does not support item assignment
咱們能夠把代碼放在python在線調式網站進行調試
把代碼稍微修改下而後再看看狀況
t = (1, 2, [30, 40]) t[2].append([50, 60]) print(t)
爲何這兩種實現會帶來不一樣的結果呢?
緣由在於t是一個元組屬於不可變對象。但用t[2]+=[50,60]的時候是對一個元組進行賦值。
因此報錯誤。
可是同時t[2]又屬於一個列表可變對象。所以數據也更新成功了
可是若是用t[2].append([50,60])的操做則是對一個列表進行操做,而並無對一個元組進行賦值。所以可以更新成功且不會報錯誤。
這是一個頗有趣的例子。
對於理解可變對象和不可變對象的操做頗有幫助。
查看背後的字節碼狀況:
import dis a = (1, 2, 3) byte_info = dis.dis('a[2]+=1') print(byte_info)
打印出反編譯的結果(反編譯後的代碼與彙編語言接近)是:
1 0 LOAD_NAME 0 (a) 2 LOAD_CONST 0 (2) 4 DUP_TOP_TWO 6 BINARY_SUBSCR 8 LOAD_CONST 1 (1) 10 INPLACE_ADD 12 ROT_THREE 14 STORE_SUBSCR 16 LOAD_CONST 2 (None) 18 RETURN_VALUE
插播一個有趣的例子
python之交換變量值
import dis def swap1(): x = 5 y = 6 x, y = y,x def swap2(): x = 5 y = 6 tmp = x x = y y = tmp if __name__ == "__main__": print ("***SWAP1***") print (dis.dis(swap1)) print ("***SWAP2***") print (dis.dis(swap2))
查看結果:
*** SWAP1*** 5 0 LOAD_CONST 1 (5) 3 STORE_FAST 0 (x) 6 6 LOAD_CONST 2 (6) 9 STORE_FAST 1 (y) 7 12 LOAD_FAST 1 (y) 15 LOAD_FAST 0 (x) 18 ROT_TWO 19 STORE_FAST 0 (x) 22 STORE_FAST 1 (y) 25 LOAD_CONST 0 (None) 28 RETURN_VALUE ***SWAP2*** 10 0 LOAD_CONST 1 (5) 3 STORE_FAST 0 (x) 11 6 LOAD_CONST 2 (6) 9 STORE_FAST 1 (y) 12 12 LOAD_FAST 0 (x) 15 STORE_FAST 2 (tmp) 13 18 LOAD_FAST 1 (y) 21 STORE_FAST 0 (x) 14 24 LOAD_FAST 2 (tmp) 27 STORE_FAST 1 (y) 30 LOAD_CONST 0 (None) 33 RETURN_VALUE
獲得結論
經過字節碼能夠看到,swap1和swap2最大的區別在於,swap1中經過ROT_TWO交換棧頂的兩個元素實現x和y值的互換,swap2中引入了tmp變量,多了一次LOAD_FAST, STORE_FAST的操做。執行一個ROT_TWO指令比執行一個LOAD_FAST+STORE_FAST的指令快,
這也是爲何swap1比swap2性能更好的緣由。
在列表和元組中,存放的是具體的對象,如整數對象,字符對象。
以下面的整數b。佔據28個字節。由於存放的是整數對象,而非整數自己。
import sys b = 1 print(sys.getsizeof(b)) # 28
對於存放大量數據來講。咱們選擇用數組的形式要好不少。
由於數組存儲的不是對象,而是數字的機器翻譯。也就是字節表述。
在array中須要規定各個字符的類型,如上表中的Type code。
定義好類型後則數組內的元素必須全是這個類型,不然會報錯。
就像下面的代碼。類型規定的是b也就是單字節的整數。
可是在插入的時候倒是c字符,則報錯:TypeError: an integer is required
import array num = array.array('b') num.append('c') print(num)
在上表中,每一個類型都有字節大小的限制。若是超出了字節大小的限制也是會報錯的
仍是b的這個類型,是有符號的單字節整數,那麼範圍是-128到127.
若是咱們插入128.則報錯:signed char is greater than maximum
提示超過了最大
import array num = array.array('b') num.append(128) print(num)
對於數組這種結構體來講,因爲佔用的內存小,所以在讀取和寫入文件的時候的速度更快,相比於列表來講的話。
下面來對比下:
首先是用列表生成並寫入txt文檔的用法
import time import struct # struct 例子 # a = 20 # 'i'表示一個int # # struct.pack把python值轉化成字節流 # data = struct.pack('i', a) # print(len(data)) # print(repr(data)) # print(data) # 拆包 # a1 = struct.unpack('i', data) # print("a1=", a1) def arry_try_list(): floats = [float for float in range(10**7)] fp = open('list.bin', 'wb') start = time.clock() for f in floats: strdata = struct.pack('i', f) fp.write(strdata) fp.close() end = time.clock() print(end - start) arry_try_list()
執行結果:5.8789385
再看數組的形式:
from array import array from random import random import time def array_try(): floats = array('d', (random() for i in range(10**7))) start = time.clock() fp = open('floats.bin', 'wb') floats.tofile(fp) fp.close() end = time.clock() print(end - start) array_try()
執行結果: 0.045192899999999994
能夠看到速度明顯提高了不少
再來對比讀文件的速度
from array import array import time def array_try(): floats = array('d') start = time.clock() fp = open('floats.bin', 'rb') # 比直接從文本文件裏面讀快,後者使用內置的float方法把每一行文字變成浮點數 floats.fromfile(fp, 10**7) fp.close() end = time.clock() print(end - start) array_try()
執行結果是:0.1172238
精準地修改了一個數組的某個字節
#memoryview內存視圖 #在不復制內容的狀況下操做同一個數組的不一樣切片 from array import array #5個短整型有符號整數的數組,類型碼是h numbers = array('h', [-2, -1, 0, 1, 2]) memv = memoryview(numbers) print(len(memv)) #轉成B類型,無符號字符 memv_oct = memv.cast('B') tolist = memv_oct.tolist() print(tolist) memv_oct[5] = 4 print(numbers)
import bisect # 以空格做爲分隔打印S中全部元素再換行. def print_all(S): for x in S: print(x, end = " ") print("") # 有序向量SV. SV = [1, 3, 6, 6, 8, 9] #查找元素. key = int(input()) print(bisect.bisect_left(SV, key)) print(bisect.bisect_right(SV, key)) # 插入新元素. key = 0 bisect.insort_right(SV, key) # 刪除重複元素的最後一個. 思考: 如何刪除第一個? key = 6 i = bisect.bisect_right(SV, key) i -= 1 # 注意此時i < len(SV)必然成立. 若是確實有key這個元素則刪除. if (i > 0 and SV[i] == key): del(SV[i]) print_all(SV) # 刪除重複key所在區間: [bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)). del SV[bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)] print_all(SV) # 無序向量USV. USV = [9, 6, 1, 3, 8, 6] # 插入新元素 key = 0 USV.append(key) # 刪除重複元素的最後一個. key = 6 # 逆向遍歷, 若是存在則刪除. i = len(USV) - 1 while i > 0: if USV[i] == key: USV[i] = USV[-1] USV.pop() break i -= 1 print_all(USV) # 刪除重複元素的第一個. try: i = USV.index(key) except: pass else: USV[i] = USV[-1] USV.pop() print_all(USV)
pickle模塊是將python值轉成成byte
try: import cPickle as pickle except: import pickle data = [{'a': 'A', 'b': 2, 'c': 3.0}] # 編碼 data_string = pickle.dumps(data) print("DATA:", data) print("PICKLE:", data_string) print(type(data_string)) # 解碼 data_from_string = pickle.loads(data_string) print(data_from_string)
雙向隊列deque
import timeit from collections import deque def way1(): mylist = list(range(100000)) for i in range(100000): mylist.pop() def way2(): mydeque = deque(range(100000)) for i in range(100000): mydeque.pop() if __name__ == "__main__": t1 = timeit.timeit("way1", setup="from __main__ import way1", number=10) print(t1) t2 = timeit.timeit("way2", setup="from __main__ import way2", number=10) print(t2)
結果:
6e-07 1.0000000000001327e-06
deque是雙向隊列,若是你的業務邏輯裏面須要大量的從隊列的頭或者尾部刪除,添加,用deque的性能會大幅提升!若是隻是小隊列,而且對元素須要隨機訪問操做,那麼list會快一些。
# 雙向隊列用法 from collections import deque # 建立一個隊列 q = deque([1]) print(q) # 往隊列中添加一個元素 q.append(2) print(q) # 往隊列最左邊添加一個元素 q.appendleft(3) print(q) # 同時入隊多個元素 q.extend([4, 5, 6]) print(q) # 在最左邊同時入隊多個元素 q.extendleft([7, 8, 9]) print(q) # 剔除隊列中最後一個 q.pop() print(q) # 刪除隊列最左邊的一個元素 q.popleft() print(q) # 清空隊列 # q.clear() # 獲取隊列長度 # length = q.maxlen # print(length)