本系列文章是一系列學習筆記,希望較為深入地分析Python3中的原理、性能,文章中絕大部分觀點都是原作作者的觀點(如下),本人對書中示例加以實踐和總結,并結合相應的Python的C語言源碼(3.6.1),分享出來。原著:
- 《High Performance Python》by O'Relly Media,作者Micha Gorelick,Ian Ozsvald
- 《Fluent Python》by O'Relly Media,作者Luciano Ramalho
深入理解各種序列(元組、列表等)能阻止我們不要重復造輪子。
序列的分類
常見的分類一般按照Mutable和Immutable分類,還可以按照:
Container sequence(元素為對象的序列):List,Tuple,collections.deque
Flat sequence(緊湊序列):str,bytes,bytearray,memoryview,array.array
列表List
List:動態數組,元素可變,可改變大小(append,resize)
列表是很容易掌握的,說一些重點的操作。
列表推導(List Comprehensions)和生成器(Generator)
列表推導和生成器是創建列表和其他序列的快速方法,能夠寫出簡介且高性能的代碼。
>>> dummy = [x for x in 'ABC']
>>> dummy
['A', 'B', 'C']
map和filter也能快速創建列表,但是在性能上并沒有優勢:
(env) MengdeiMac:02-array-seq an$ cat listcomp_speed.py
import timeit
TIMES = 10000
SETUP = """
symbols = '$¢£¥€¤'
def non_ascii(c):
return c > 127
"""
def clock(label, cmd):
res = timeit.repeat(cmd, setup=SETUP, number=TIMES)
print(label, *('{:.3f}'.format(x) for x in res))
clock('listcomp :', '[ord(s) for s in symbols if ord(s) > 127]')
clock('listcomp + func :', '[ord(s) for s in symbols if non_ascii(ord(s))]')
clock('filter + lambda :', 'list(filter(lambda c: c > 127, map(ord, symbols)))')
clock('filter + func :', 'list(filter(non_ascii, map(ord, symbols)))')
(env) MengdeiMac:02-array-seq an$ python listcomp_speed.py
listcomp : 0.012 0.013 0.013
listcomp + func : 0.017 0.018 0.032
filter + lambda : 0.020 0.016 0.025
filter + func : 0.015 0.020 0.025
創建元組、arrays等序列時,也可以通過列表推導來做,但是使用生成器可以更加節省內存。(通過iterator protocal生成元素,而不是全部生成放在內存里)
元組Tuple
** 元組不僅僅是“不可修改的列表”(Immutable List)**
** 元組也可以被用作“無屬性的記錄”(Records with no field name)**
Tuple as Records
如果只是把元組看作不可變的列表,那么元素的順序并不是很重要。我們可以把元組看作一系列的屬性,屬性的數量是固定的,位置也是重要的。
(name, age) = ('Jack', 18)
我們可以通過位置獲取相應的屬性,書中還介紹了Named Tuple,collections .nametuple,可以賦予屬性名字,可以通過名字或位置來訪問屬性,比Object更輕量。
Tuple as Immutable List
Tuple支持所有的List的方法,除了add,delete,reverse。
每一個Python程序員都知道,序列可以切片,像這樣a[start:stop],一些不那么出名知識點。
為什么slice和range不包括最后一個元素
- 這樣更容易得到slice的長度 = stop - start
- 更容易將序列分割成兩個部分,a[:3]和a[3:]
>>> a = [1,2,3,4,5]
>>> a[:3]
[1, 2, 3]
>>> a[3:]
[4, 5]
序列的賦值+=, *=
+= 依賴iadd的實現,也就是inplace addition
*= 依賴imul的實現
對于可變序列(List),inplace的操作都實現的很好,對于不可變序列(Tuple),沒有實現。
>>> l=[1,2,3]
>>> id(l)
4322512072
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
4322512072
>>>
>>>
>>> t=(1,2,3)
>>> id(t)
4322621768
>>> t *= 2
>>> t
(1, 2, 3, 1, 2, 3)
>>> id(t)
4322550888
>>>
也就是,對于一個不可變序列, 重復的粘貼操作是非常低效的,伴有很多的內存分配和拷貝操作。
還有一個corner case,想想輸出是為什么?既拋出異常,tuple也被改變了,結論就是,不要讓tuple中有可變的對象,疊加賦值操作不是原子操作。
>>> l=(1,2,[30,50])
>>> l[2] += [50,60]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> l
(1, 2, [30, 50, 50, 60])
>>>
list.sort vs sort
有兩個排序函數:
列表自帶的函數,list.sort,對一個列表進行原地排序,也就是,不生成一個新的拷貝。
內置的sort函數,sort,創建一個新的列表,排序,并且返回新列表。
還有一點重要的常識:
functions or methods that change an object in place should return None to make it clear to the caller that the object itself was changed, and no new object was created.