本系列文章是一系列學(xué)習(xí)筆記,希望較為深入地分析Python3中的原理、性能,文章中絕大部分觀點(diǎn)都是原作作者的觀點(diǎn)(如下),本人對書中示例加以實(shí)踐和總結(jié),并結(jié)合相應(yīng)的Python的C語言源碼(3.6.1),分享出來。原著:
- 《High Performance Python》by O'Relly Media,作者M(jìn)icha Gorelick,Ian Ozsvald
- 《Fluent Python》by O'Relly Media,作者Luciano Ramalho
深入理解各種序列(元組、列表等)能阻止我們不要重復(fù)造輪子。
序列的分類
常見的分類一般按照Mutable和Immutable分類,還可以按照:
Container sequence(元素為對象的序列):List,Tuple,collections.deque
Flat sequence(緊湊序列):str,bytes,bytearray,memoryview,array.array
列表List
List:動態(tài)數(shù)組,元素可變,可改變大?。╝ppend,resize)
列表是很容易掌握的,說一些重點(diǎn)的操作。
列表推導(dǎo)(List Comprehensions)和生成器(Generator)
列表推導(dǎo)和生成器是創(chuàng)建列表和其他序列的快速方法,能夠?qū)懗龊喗榍腋咝阅艿拇a。
>>> dummy = [x for x in 'ABC']
>>> dummy
['A', 'B', 'C']
map和filter也能快速創(chuàng)建列表,但是在性能上并沒有優(yōu)勢:
(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
創(chuàng)建元組、arrays等序列時(shí),也可以通過列表推導(dǎo)來做,但是使用生成器可以更加節(jié)省內(nèi)存。(通過iterator protocal生成元素,而不是全部生成放在內(nèi)存里)
元組Tuple
** 元組不僅僅是“不可修改的列表”(Immutable List)**
** 元組也可以被用作“無屬性的記錄”(Records with no field name)**
Tuple as Records
如果只是把元組看作不可變的列表,那么元素的順序并不是很重要。我們可以把元組看作一系列的屬性,屬性的數(shù)量是固定的,位置也是重要的。
(name, age) = ('Jack', 18)
我們可以通過位置獲取相應(yīng)的屬性,書中還介紹了Named Tuple,collections .nametuple,可以賦予屬性名字,可以通過名字或位置來訪問屬性,比Object更輕量。
Tuple as Immutable List
Tuple支持所有的List的方法,除了add,delete,reverse。
每一個(gè)Python程序員都知道,序列可以切片,像這樣a[start:stop],一些不那么出名知識點(diǎn)。
為什么slice和range不包括最后一個(gè)元素
- 這樣更容易得到slice的長度 = stop - start
- 更容易將序列分割成兩個(gè)部分,a[:3]和a[3:]
>>> a = [1,2,3,4,5]
>>> a[:3]
[1, 2, 3]
>>> a[3:]
[4, 5]
序列的賦值+=, *=
+= 依賴iadd的實(shí)現(xiàn),也就是inplace addition
*= 依賴imul的實(shí)現(xiàn)
對于可變序列(List),inplace的操作都實(shí)現(xiàn)的很好,對于不可變序列(Tuple),沒有實(shí)現(xiàn)。
>>> 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
>>>
也就是,對于一個(gè)不可變序列, 重復(fù)的粘貼操作是非常低效的,伴有很多的內(nèi)存分配和拷貝操作。
還有一個(gè)corner case,想想輸出是為什么?既拋出異常,tuple也被改變了,結(jié)論就是,不要讓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
有兩個(gè)排序函數(shù):
列表自帶的函數(shù),list.sort,對一個(gè)列表進(jìn)行原地排序,也就是,不生成一個(gè)新的拷貝。
內(nèi)置的sort函數(shù),sort,創(chuàng)建一個(gè)新的列表,排序,并且返回新列表。
還有一點(diǎn)重要的常識:
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.