《Fluent Python》 读书笔记:序列

2021-12-05
标签: PYTHON

本章主要讨论序列(sequence)

这篇文章 也介绍了序列类型的特点。

内置序列的类别

内置序列(built-in sequence)类型可以分为以下几种:

容器序列

看起来能存放任意类型对象,其实存放的只是对任意类型对象的引用。

比如:list,tuple,collections.deque

扁平序列

一段连续的内存空间,存放的是真实的值,而不是引用。只能存放字符、字节、数值等基础类型。

比如:str, bytes, bytesarray, memoryview, array.array

tuple 的存在意义

许多 Python 教程中把 tuple 称为“不可变的list”,然而这并没有完全概括 tuple 的特点。

tuple 除了看作不可变的列表,还可以用于没有字段名的记录:元组中每个元素的记录都存放了记录中一个字段的数据,所以元素的位置也赋予了信息。

如果仅仅把 tuple 理解为列表,那么元素的个数和他们的位置就没有意义,但是如果把 tuple 当作一些字段的集合,那么数量和位置信息就变得非常重要了。

tuple 拆包

拆包 unpacking 的写法简洁又美观,有下面一些使用场景。

常规拆包

常规的拆包方式,如下所示:

a, b = ('1', '2')

%号 拆包

除了常规拆包之外,还有其他的方式,比如:

>>> print('%s: %s' % ('1', '2'))
1: 2

下划线占位

下划线作为占位符。如果我们只对 tuple 中一些位置的元素感兴趣,而对其他位置的没有用,那么可以用下划线做占位符。比如:

>>> a, _, _ = ('1', '2', '3')
>>> a
'1'

星号 * 收集

类似函数的参数写法,用星号 * 可以收集剩余位置的元素。

>>> a, *others = ('1', '2', '3')
>>> a
'1'
>>> others
['2', '3']

因为 * 号是将当前对应位置的所有元素收集在一起,所以一次赋值中,不能出现多个星号 *

>>> a, *others, *another = ('1', '2', '3')
  File "<stdin>", line 1
SyntaxError: multiple starred expressions in assignment
>>>

但是星号* 可以出现在任意位置:

>>> a, *others, end = ('1', '2', '3', '4')
>>> a
'1'
>>> end
'4'
>>> others
['2', '3']
>>>

嵌套拆包

>>> a, (b, c), d = ('1',('2','3'),'4')
>>> a
'1'
>>> b
'2'
>>> c
'3'
>>> d
'4'
>>>

slicing 的哲学

序列(sequence)的切片操作(slicing) 在 这篇文章 里有介绍

[m:n) 的原因

最后一个元素是开区间,不包括在切片的结果里,比如

>>> ('1', '2', '3', '4')[:2]
('1', '2')

这样做的好处是:

  • 看出元素个数: [:n] 有 n个元素, [m:n] 有 n-m 个元素
  • 分成两部分:[:n][n:] 刚好是不重叠的两部分

列表的列表

如果想生成如下的一个列表(井字游戏的棋盘),该怎么写代码:

[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

第一种:

>>> [['_'] * 3] * 3
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

第二种:

>>> [['_'] * 3 for i in range(3)]
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]

看起来是一样的结果,但是第一种里三个list其实是同一个对象,如果修改其中任何一个,另外两个将同时被修改。

>>> wrong = [['_'] * 3] * 3
>>> wrong[0][2] = 'X'
>>> wrong
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
>>>

三个同时被修改。

而第二种的三次循环里生成了三个不同的对象,可以独立的修改而不相互影响。

>>> correct = [['_'] * 3 for i in range(3)]
>>> correct[0][2] = 'X'
>>> correct
[['_', '_', 'X'], ['_', '_', '_'], ['_', '_', '_']]
>>>

这篇文章 也提到过类似的事情。

序列的自增

可变序列普遍实现了自增:

list 的例子

>>> a=[1, 2]
>>> a+=[3]
>>> a
[1, 2, 3]
>>>

str 的例子

>>> a='abc'
>>> a+='d'
>>> a
'abcd'
>>>

列表的替代品

列表使用起来非常方便,但是在一些场合下并不是最优选择。

array

如果需要一个只包含数字的列表,那么 array.array 比 list 更高效。这里的高效,有时间和空间两个方面。

从一个二进制文件里读出1000万个double float只需要0.1秒,比文本文件快60倍

memoryview

memoryview 的概念和array类似,能用不同方式读写同一块内存数据,而且内容不会随意移动。

NumPy 和 SciPy

NumPy 提供了对高阶数组和矩阵的操作。

SciPy 则为线性代数、数值积分和统计学而设计。

deque

list.pop(0) 这种删除列表第一个元素的效率是很低的,因为会牵涉到移动列表的全部元素。deque 用来快速从两端添加或者删除元素的数据类型。

本章小结

要想写出准确、高效和地道的 Python 代码,掌握标准序列类型(sequence)是不可或缺的。

序列可以分为可变、不可变两类,也可以分为扁平和容器型两类。容器序列遇到可变对象时就要额外小心,这两种组合容易搞出一些意外。

列表推导和生成器并不难,而且用起来使人上瘾。

元组 tuple 扮演了两类角色,即可以用作无名称字段的记录,有可以看作不可变的列表。

元组的拆包赋值是最安全、可靠的提取字段信息的方法。

除了列表和元组,标准库里还有 array.array、collections.deque 等类型。

杂谈

元组的本质

Practicality beats purity.
实用胜过纯粹。

ABC 语言里的 compounds 类是 Python 元组的鼻祖,但是compounds 不属于序列,不是 iterable,不能通过下标取值,不能 slicing。要么当做整体来用,要么通过平行赋值把所有字段取出来。compounds 是个纯粹的设计,然而 Python 的 tuple 更为实用。这体现:

优雅和简约

Elegance begets simplicity.
优雅是简约之父

*extra 这种语法将多个元素赋值给一个参数,并且取代了 Python 里的 apply 函数。如今到了 Python3 里,又可以用在赋值表达式的左侧,这让本来就很实用的语法锦上添花。

这样的改进让 Python 变得越来越灵活,越来越统一。

key参数

list.sort、sorted、max 和 min 函数的 key 参数是个很棒的设计。其他语言里的排序函数需要用户提供双参数的函数,例如 cmp(a, b),简单而高效。

说它简单,是因为只需要提供一个单参数的函数即可。说它高效,是因为在每个元素上,key 函数只会被调用一次。

另外,key 函数也能让你对一个混合序列排序:

>>> l = ['28',14,'5']
>>> sorted(l, key=str)
[14, '28', '5']
>>> sorted(l, key=int)
['5', 14, '28']
>>>

关于 Tim 的八卦

sorted 和 list.sort 背后的排序算法是 Timsort,它是一种自适应算法,根据原始数据的顺序特点交替使用插入排序和归并排序。

Timsort 的作者是 Tim Peters,是一位高产的 Python 核心开发者,同时也是 Python 之禅(import this)的作者。

s = """Gur Mra bs Clguba, ol Gvz Crgref
Ornhgvshy vf orggre guna htyl.
Rkcyvpvg vf orggre guna vzcyvpvg.
Fvzcyr vf orggre guna pbzcyrk.
Pbzcyrk vf orggre guna pbzcyvpngrq.
Syng vf orggre guna arfgrq.
Fcnefr vf orggre guna qrafr.
Ernqnovyvgl pbhagf.
Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.
Nygubhtu cenpgvpnyvgl orngf chevgl.
Reebef fubhyq arire cnff fvyragyl.
Hayrff rkcyvpvgyl fvyraprq.
Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.
Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.
Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.
Abj vf orggre guna arire.
Nygubhtu arire vf bsgra orggre guna *evtug* abj.
Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.
Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.
Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!"""

d = {}
for c in (65, 97):
    for i in range(26):
        d[chr(i+c)] = chr((i+13) % 26 + c)

print("".join([d.get(c, c) for c in s]))

如果您对本站内容有疑问或者寻求合作,欢迎 联系邮箱邮箱已到剪贴板

标签: PYTHON

欢迎转载本文,惟请保留 原文出处 ,且不得用于商业用途。
本站 是个人网站,若无特别说明,所刊文章均为原创,并采用 署名协议 CC-BY-NC 授权。