本系列文章是一系列学习笔记,希望较为深入地分析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.