Fluent Python 笔记(二):序列基础

458 阅读4分钟
原文链接: zhuanlan.zhihu.com

目录:

Fluent Python 笔记(一):数据模型

Fluent Python 笔记(二):序列基础

Fluent Python 笔记(三):高效操作序列

本篇开始总结 Python 基本的数据结构,大部分情况下,这些数据结构就已经够用了,不需要重复造轮子。首先是序列及其相关的操作。

序列(Sequence)

标准库中提供了很多序列类型,都是C实现的,效率很高。

  • 容器序列(Container sequences):

list, tuple, collections.deque ...

容器序列持有的是所包含对象的引用,可以是任意类型。

  • 平坦序列(Flat sequences):

str, bytes, bytearray, memoryview, array.array

平坦序列物理上存储每个条目对应的内存空间的值,而不是作为不同的对象。

因此,平坦序列更加紧凑,但是他们只能保存原始的数值如字符,字节和数字。

根据可不可变分类:

可变的序列:

list, bytearray, array.array, collections.deque, memoryview

不可变序列:

tuple, str, bytes

如何快速生成一个序列?

  • 列表推导(List Comprehensions)
  • 生成器表达式(Generator Expressions)

强烈推荐列表推导,用它实现的代码简洁,易读,执行效率高,堪称完美。

首先举一个例子:

取出一个字符串列表中所有小写的字符串,并组成列表

>>> words = ['The', 'quick', 'BROWN', 'Fox', 'jumped', 'OVER', 'the', 'Lazy', 'DOG']
>>> [word for word in words if word.islower()]
['quick', 'jumped', 'the']

列表推导格式以 [] 为标志,内容分为三部分 [A B C],A,B,C分别代表一个表达式,其中C可以省略。

对于上面的例子:

  • A:word
  • B:for word in words
  • C:if word.islower()

首先看B,B的格式一般为 for x in xxx,表示从一个序列中逐个选取元素,也就是常用的 for in 结构。可以有多个for in,比如 for x in xxx for y in yyy。使用多个for的时候,就会生成所有组合。例如列举出不同颜色尺寸的T恤组合 :

>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> tshirts = [(color, size) for color in colors for size in sizes]
>>> tshirts
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'),
('white', 'M'), ('white', 'L')]

然后看C,C提供一个判断条件,格式一般为 if xxx,其中可以用到B所给出的x,用于选取符合条件的条目。可以省略,也就意味着使用B生成的所有条目。

最后看A,A格式随意,可以使用B中给出的x(或者y),当然也可以不使用。

>>> [word[0] for word in words if word.islower()]
['q', 'j', 't']
>>> [3 for word in words if word.islower()]
[3, 3, 3]

列表推导时一般控制在一行以内,如果只有多行才能实现,那说明逻辑太复杂了,考虑别用列表推导了,展开吧。

生成器表达式和列表推导唯一的不同是用 () 包围而不是 [],如果不需要一次性生成整个列表,那么用生成器表达式更好。如下:

>>> (word for word in words if word.islower())
<generator object <genexpr> at 0x02F00288>

这样只是构造了一个生成器对象,每个元素会在需要用到的时候才进行构造,可以原封不动地用于 for in 结构,需要变成列表的时候也可以随时调用 list() 函数转化为列表。

元组(Tuple)

元组的主要用途:

  • 作为不可变的列表
  • 作为没有字段名称的记录

元组解包:

>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates # tuple unpacking
>>> latitude
33.9425
>>> longitude
-118.408056

作为函数参数就地展开,在前面加*就可以了:

>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
具名元组(Named Tuples):

因为元组作为记录比较好用,因此出现了 namedtuple,在 collections 模块中。

使用 namedtuple 创建的实例消耗的内存和普通元组相同,因为字段的名字是存储在类中的。他们使用的内存比普通的类要少,因为它们不用在每个实例的 __dict__ 中存储属性。

使用示例:

>>> from collections import namedtuple
>>> Point = namedtuple('Point', 'x y')
>>> p = Point(3, 4)
>>> p
Point(x=3, y=4)
>>> p.x
3
>>> p[1]
4

namedtuple 有几个有用的属性和方法:

  • _fields 类属性,保存所有字段名称
>>> Point._fields
('x', 'y')
  • _make(iterable) 类方法,使用已经存在的序列或者 iterable 来创建 namedtuple
>>> point_tuple = (3, 4)
>>> p = Point._make(point_tuple)
>>> p
Point(x=3, y=4)
  • _asdict() 实例方法,返回一个 OrderedDict,映射名称和对应的值。
>>> p._asdict()
OrderedDict([('x', 3), ('y', 4)])

总结:

Python 中序列的用法是大同小异的,基本上掌握了一个,其他的都差不多,剩下的就是可不可变,存储的是值还是引用需要注意下。

使用列表推导来构建序列能够让代码变得非常简洁,容易理解,推荐多尝试使用。

元组用来作为数据记录比较好用,namedtuple 使得元组在保持低内存消耗的情况下更加容易调试。

(下一篇介绍序列的通用操作,包括切片,操作符相关,以及一些能够提高效率的函数