fluent python-第 2 章 2.1 内置序列构成的数组

序列类型

Python 标准库用 C 实现了丰富的序列类型, 列举如下。
容器序列
list、 tuple 和 collections.deque 这些序列能存放不同类型的数据。

扁平序列
str、 bytes、 bytearray、 memoryview 和 array.array, 这类序列只能容纳一种类型。

容器序列存放的是它们所包含的任意类型的对象的引用, 而扁平序列
里存放的是值而不是引用
。 换句话说, 扁平序列其实是一段连续的内存空间。 由此可见扁平序列其实更加紧凑, 但是它里面只能存放诸如字符、 字节和数值这种基础类型。序列类型还能按照能否被修改来分类。

可变序列
list、 bytearray、 array.array、 collections.deque 和
memoryview。
不可变序列
tuple、 str 和 bytes。
图 2-1 显示了可变序列( MutableSequence) 和不可变序列
( Sequence) 的差异, 同时也能看出前者从后者那里继承了一些方
法。 虽然内置的序列类型并不是直接从 Sequence 和
MutableSequence 这两个抽象基类( Abstract Base Class, ABC) 继承而来的, 但是了解这些基类可以帮助我们总结出那些完整的序列类型包含了哪些功能。


2.2 列表推导和生成器表达式

说白了主要就是 for 循环


2.2.3 笛卡儿积

>>> 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')]
>>> for color in colors: ?
... for size in sizes:
... print((color, size))
...
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')
>>> tshirts = [(color, size) for size in sizes ?
... for color in colors]
>>> tshirts
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'),
('black', 'L'), ('white', 'L')]


2.2.4 生成器表达式

生成器表达式的语法跟列表推导差不多, 只不过把方括号换成圆括号而

>>> symbols = '$¢£¥€¤'
>>> tuple(ord(symbol) for symbol in symbols) ?
(36, 162, 163, 165, 8364, 164)
>>> import array
>>> array.array('I', (ord(symbol) for symbol in symbols)) ?
array('I', [36, 162, 163, 165, 8364, 164])


2.3 元组不仅仅是不可变的列表

有些 Python 入门教程把元组称为“不可变列表”, 然而这并没有完全概括
元组的特点。 除了用作不可变的列表, 它还可以用于没有字段名的记
录。 鉴于后者常常被忽略, 我们先来看看元组作为记录的功用


2.3.1 元组和记录
元组其实是对数据的记录: 元组中的每个元素都存放了记录中一个字段
的数据, 外加这个字段的位置。 正是这个位置信息给数据赋予了意义。

In [210]: lax_coordinates = (33.9425, -118.408056)

In [211]: city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)

In [212]: traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'),('ESP', 'XDA2
     ...: 05856')]

In [213]: for passport in sorted(traveler_ids):
     ...:     print('%s/%s' % passport)
     ...:
BRA/CE342567
ESP/XDA205856
USA/31195855

In [214]:  for country, _ in traveler_ids:     # 说白了就是花式运用拆包特性
     ...:     print(country)      # 上方 _ 为占位符 
     ...:
USA
BRA
ESP


2.3.2 元组拆包

最好辨认的元组拆包形式就是平行赋值, 也就是说把一个可迭代对象里的元素, 一并赋值到由对应的变量组成的元组中。

>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates       # ??元组拆包
>>> latitude
33.9425
>>> longitude
-118.408056

另外一个很优雅的写法当属不使用中间变量交换两个变量的值:

>>> b, a = a, b

在 Python 中, 函数用 *args 来获取不确定数量的参数算是一种经典写
法了。

>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(3)
>>> a, b, rest
(0, 1, [2])
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])


2.3.3 嵌套元组拆包

In [221]: a,b,c = (1,2,(3,4))

In [222]: a
Out[222]: 1

In [223]: c
Out[223]: (3, 4)


2.3.4 具名元组

collections.namedtuple 是一个工厂函数, 它可以用来构建一个带
字段名的元组和一个有名字的类——这个带名字的类对调试程序有很大
帮助。
用 namedtuple 构建的类的实例所消耗的内存跟元组是一样的, 因为字段名都被存在对应的类里面。 这个实例跟普通的对象实例比起来也要小一些, 因为 Python 不会用 __dict__ 来存放这些实例的属性。

Card = collections.namedtuple('Card', ['rank', 'suit'])   # 构建范例

如何用具名元组来记录一个城市的信息:

>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates') ?
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) ?
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722,
139.691667))
>>> tokyo.population ?
36.933
>>> tokyo.coordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP

? 创建一个具名元组需要两个参数, 一个是类名, 另一个是类的各个
字段的名字。 后者可以是由数个字符串组成的可迭代对象, 或者是由空格分隔开的字段名组成的字符串。

? 存放在对应字段里的数据要以一串参数的形式传入到构造函数中
( 注意, 元组的构造函数却只接受单一的可迭代对象)
? 你可以通过字段名或者位置来获取一个字段的信息。


建立由列表组成的列表

有时我们会需要初始化一个嵌套着几个列表的列表, 譬如一个列表可能需要用来存放不同的学生名单, 或者是一个井字游戏板 上的一行方
块。 想要达成这些目的, 最好的选择是使用列表推导。

一个包含 3 个列表的列表, 嵌套的 3 个列表各自有 3 个元素来代表井字游戏的一行方块 :

>>> board = [['_'] * 3 for i in range(3)]  ?
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'  ?
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]

? 建立一个包含 3 个列表的列表, 被包含的 3 个列表各自有 3 个元
素。 打印出这个嵌套列表。
? 把第 1 行第 2 列的元素标记为 X, 再打印出这个列表。


2.6 序列的增量赋值

+= 背后的特殊方法是 __iadd__ ( 用于“就地加法”) 。 但是如果一个类
没有实现这个方法的话, Python 会退一步调用 __add__ 。 考虑下面这
个简单的表达式:

>>> a += b

如果 a 实现了 __iadd__ 方法, 就会调用这个方法。 同时对可变序列
( 例如 list、 bytearray 和 array.array) 来说, a 会就地改动, 就
像调用了 a.extend(b) 一样。 但是如果 a 没有实现 __iadd__的话, a += b 这个表达式的效果就变得跟 a = a + b 一样了: 首先计算 a + b, 得到一个新的对象, 然后赋值给 a。 也就是说, 在这个表达式中,变量名会不会被关联到新的对象, 完全取决于这个类型有没有实现
__iadd__这个方法。

总体来讲, 可变序列一般都实现了__iadd__方法, 因此 += 是就地加法。 而不可变序列根本就不支持这个操作, 对这个方法的实现也就无从谈起。

接下来有个小例子, 展示的是 *= 在可变和不可变序列上的作用:

>>> l = [1, 2, 3]
>>> id(l)
4311953800 ?
>>> l *= 2
>>> l
[1, 2, 3, 1, 2, 3]
>>> id(l)
4311953800 ?
>>> t = (1, 2, 3)
>>> id(t)
4312681568 ? # 元组不可变,一变内存地址就变
>>> t *= 2
>>> id(t)     #   对不可变序列进行重复拼接操作的话, 效率会很低
4301348296 ? 运用增量乘法后, 新的元组被创建


??一个关于+=的谜题 (有趣)

In [257]: t = (1,2,[30,40])

In [258]: t[2]
Out[258]: [30, 40]

In [259]: type(t)
Out[259]: tuple

In [260]: type(t[2])
Out[260]: list

In [262]: t[2] += [50,60]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-262-b483a1b4fe1d> in <module>()
----> 1 t[2] += [50,60]
TypeError: 'tuple' object does not support item assignment

结果:
t 变成 (1, 2, [30, 40, 50, 60])
因为 tuple 不支持对它的元素赋值, 所以会抛出 TypeError 异常

教训:不要把可变对象放在元组里面


2.7 list.sort方法和内置函数sorted

list.sort 方法会就地排序列表, 也就是说不会把原列表复制一份。 这
也是这个方法的返回值是 None 的原因, 提醒你本方法不会新建一个列
表。 在这种情况下返回 None 其实是 Python 的一个惯例.

与 list.sort 相反的是内置函数 sorted, 它会新建一个列表作为返回值。 这个方法可以接受任何形式的可迭代对象作为参数, 甚至包括不可变序列或生成器


2.10 本章小结

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

Python 序列类型最常见的分类就是可变和不可变序列。 但另外一种分类方式也很有用, 那就是把它们分为扁平序列和容器序列。 前者的体积更小、 速度更快而且用起来更简单, 但是它只能保存一些原子性的数据,比如数字、 字符和字节。 容器序列则比较灵活, 但是当容器序列遇到可变对象时, 用户就需要格外小心了, 因为这种组合时?;岣愠鲆恍耙馔狻?, 特别是带嵌套的数据结构出现时, 用户要多费一些心思来保证代码的正确

列表推导和生成器表达式则提供了灵活构建和初始化序列的方式, 这两个工具都异常强大。 如果你还不能熟练地使用它们, 可以专门花时间练习一下。 它们其实不难, 而且用起来让人上瘾元组在 Python 里扮演了两个角色, 它既可以用作无名称的字段的记录,又可以看作不可变的列表。 当元组被当作记录来用的时候, 拆包是最安全可靠地从元组里提取不同字段信息的方式。 新引入的*句法让元组拆包的便利性更上一层楼, 让用户可以选择性忽略不需要的字段。 具名元组也已经不是一个新概念了, 但它似乎没有受到应有的重视。 就像普通元组一样, 具名元组的实例也很节省空间, 但它同时提供了方便地通过名字来获取元组各个字段信息的方式, 另外还有个实用的 ._asdict()方法来把记录变成 OrderedDict 类型。

Python 里最受欢迎的一个语言特性就是序列切片, 而且很多人其实还没完全了解它的强大之处。 比如, 用户自定义的序列类型也可以选择支持NumPy 中的多维切片和省略( ...) 。 另外, 对切片赋值是一个修改可变序列的捷径。

重复拼接 seq * n 在正确使用的前提下, 能让我们方便地初始化含有
不可变元素的多维列表。 增量赋值 += 和 *= 会区别对待可变和不可变序列。 在遇到不可变序列时, 这两个操作会在背后生成新的序列。 但如果被赋值的对象是可变的, 那么这个序列会就地修改——然而这也取决于序列本身对特殊方法的实现。

序列的 sort方法和内置的 sorted函数虽然很灵活, 但是用起来都不
难。 这两个方法都比较灵活, 是因为它们都接受一个函数作为可选参数来指定排序算法如何比较大小, 这个参数就是 key 参数。 key 还可以被用在 min 和 max 函数里。 如果在插入新元素的同时还想保持有序序列的顺序, 那么需要用到bisect.insort。 bisect.bisect的作用则是快速查找。

除了列表和元组, Python 标准库里还有 array.array。 另外, 虽然NumPy 和 SciPy 都不是 Python 标准库的一部分, 但稍微学习一下它们,会让你在处理大规模数值型数据时如有神助。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容