流畅的Python读书笔记-第2章-序列类型(list、tuple等)

第2章 序列构成的数组

2.1 序列类型概述

Python标准库用C实现了丰富的序列类型

  • 容器序列

    listtuplecollections.deque这些序列能存放不同类型的数据

  • 扁平序列

    strbytesbytearraymemoryviewarray.array这些序列只能容纳一种类型

容器序列存放的是对象的引用而扁平序列里存放的是对象的值而不是引用。即,扁平序列其实是一段连续的内存空间,但它里面只能存放诸如字符、字节和数值这种基本类型。

从序列类型是否能被修改,可以分为:

  • 可变序列(Mutable Sequence)

    listbytearrayarray.arraycollections.dequememoryview

  • 不可变序列(Sequence)

    tuplestrbytes

2.2 列表推导

下面介绍list,开始于列表推导(list comprehension)

>>> x = 'ABC'
>>> dummy = [ord(x) for x in x]

注意:在python2.x中,列表推导里for关键词后的赋值操作可能会影响列表推导上下文中的同名变量,但这一问题在Python3中不会造成影响。

句法提示:Python会忽略代码里[]{}()中的换行。(Python中的续行符:\

列表推导同filtermap的比较
>>> symbols = '$¢£¥€¤'
>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))

可见,虽然filtermap结合起来也能达到列表推导的效果,但明显更复杂,而且其效率也不一定比列表推导快。

总之,列表推导的作用只有一个:生成列表,如果想生成其他序列类型,就要用到生成器表达式。

2.3 生成器表达式

要生成一个tuplearray.array等序列类型,显然我们可以先用列表推导生成一个list,然后再将list转成其他序列类型。但生成器表达式是更好的选择。这是因为生成器表达式可以逐个的产出元素,而不是先建立一个完整的列表,然后再将其传入某个构造函数里。显然生成器表达式更能节省内存。

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

>>> symbols = '$¢£¥€¤'
>>> tuple(ord(x) for x in symbols)
>>> import array
>>> array.array('I', (ord(x) for x in symbols))		# 第一个参数I表示数组中存储值的类型是long

注: 当生成器表达式是函数调用的唯一参数时,可以省略括号。

2.4 元组

关于元组,首先要明确的一点是:元组不仅仅是不可变的列表

元组可用于对数据的记录

元组中每个元素都有其对应的位置,正是这一点能让我们可以将元组当成记录来用,即赋予元组每个位置一个具体的含义。

>>> lax_coordinates = (33.9425, -118.4080)
>>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)
>>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDZ208556')]
>>> for country, _ in traveler_ids:
... 	print(country)

注: _ 是占位符。

元组拆包

在上例中,我们把('Tokyo', 2003, 32450, 0.66, 8014)里的元素分别赋给了变量city, year, pop, chg, area, 只用一行代码就完成了所有的赋值工作,让我们再来看元组拆包的例子:

>>> lax_coordinates = (33.9425, -118.408056)
>>> lattitude, longitude = lax_coordinates

在Python中可以这样交换两个变量的值:

>>> b, a = a, b

上述两个都是平行赋值的例子,所谓平行赋值,就是把一个可迭代对象里的元素,一并赋值到对应的变量组成的元组中去。

此外,还可以*将一个可迭代对象拆开作为函数的参数

>>> divmod(20, 8)  # divmod(a, b) 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
>>> quotient, remainder = divmod(*t)
>>> quotient, remainder
(2, 4)

或者用*来处理剩下的元素

>>> a, b, *rest = range(5)
>>> a, b, rest	# rest为什么是一个list呢?
(0, 1, [2, 3, 4])	
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])

注: 在平行赋值中,*前缀只能用在一个变量前面,但这个变量可以出现在赋值表达式的任意位置:

>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
嵌套元组拆包

接受表达式的元组本身可以是嵌套的,如(a, b, (c, d))。只要这个接受元组的嵌套结构符合表达式本身的嵌套结构,Python就可以作出正确的对应。

>>> metro_area = ('Tokyo', 'Japan', 36.933, (35.689, 139.691))
>>> name, cc, pop, (lat, lon) = metro_area
('Tokyo', 'Japan', 36.933, (35.689, 139.691))
具名元组

前面提到,元组可用于对数据的记录,每一个位置都可以有表达的含义。在Python中,我们可以对记录中的字段命名,这需要用到collections.namedtuple, 这是一个工厂函数。它可以用来创建一个带字段名的元组和一个有名字的类

>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates')
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689, 139.6916))
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689, 139.6916))
>>> tokyo.country
'JP'
>>> tokyo[3]
(35.689, 139.6916)
  • 创建一个具名元组需要两个参数:类名和类各个字段的名字。后者可以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。

  • 可以通过字段名或者位置来获取对应的元素

具名元组具有一些自己专属的属性:_fields类属性、类方法_make(iterable)、和实例方法_asdict()

>>> City._fields
('name', 'country', 'population', 'coordinates')
>>> delhi_data = ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
>>> delhi = City._make(delhi_data)
>>> delhi._asdict()
{'name': 'Delhi NCR',
 'country': 'IN',
 'population': 21.935,
 'coordinates': (28.613889, 77.208889)}
  • _fields类属性是一个包括这个类所有字段名称的元组
  • _make()通过接受一个可迭代对象来生成类的一个实例,作用相当于City(*delhi_data)
  • _asdict()将具名元组的信息以dict的形式返回
元组和列表的相似度

除了不支持增减元素相关的方法外,元组支持列表的其他所有方法。

2.5 切片

在python中,列表、元组、字符串这类序列类型都支持切片操作。

切片和区间操作会忽略最后一个元素:这符合C语言以0为起始下标的传统,即左闭右开区间。

这样做其实是有几点好处的:

  • 当只有最有一个位置信息时,很容易看出来有几个元素。例如range(3)mylist[:3]都有3个元素。
  • 当起始位置都知道时,可以用 end-start 快速计算区间长度。
  • 可以用任一下标将序列分成不重叠的两部分,只要写成mylist[:x]mylist[x:]就可以了。
具体的切片操作

可以用s[a:b:c]的形式从s中在a和b之间以c为间隔取值。同时c的值可以为负,负意味着反向取值。

>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'

a:b:c这种用法只能作为索引或者下标用在[]中返回一个切片对象:slice(a, b, c)。当然我们也可以手动地对切片命名,例如:

>>> SKU = slice(0, 2)
>>> s[SKU]
'bi'
多维切片和省略(…)
给切片赋值

如果切片放在赋值语句的左边,或者把它作为del操作的对象,就可以对序列进行嫁接、切除、就地修改等。

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]

:如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象,即使是单独的一个值,也需要将其转换成可迭代对象。

2.6 对序列使用+*

>>> l = [1, 2, 3]
>>> l * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5 * 'abcd'
'abcdabcdabcdabcdabcd'

+*都不会修改原有序列,而是将其复制几份再拼接起来。即构建一个全新的序列。

但要注意使用 a*n这种语句时,如果序列a里的元素是其他可变对象的引用时,结果可能会出乎意料。

2.7 序列的增量赋值

增量赋值运算符+=*=的表现取决于他们的第一个操作对象。

+= 背后是特殊方法 __iadd__(就地加法)。但是如果一个类没有实现 __iadd__, 则Python会退一步调用 __add__

>>> a += b

对于上面这个表达式,如果a是可变对象,那么会就地改动。但如果a 没有实现__iadd__, 则这个表达式的效果变得和a = a + b一样了:首先计算a+b,得到一个新的对象,再赋值给a。一般来说,可变序列都实现了 __iadd__方法,对于不可变序列来说,根本就不支持这个操作。

关于增量赋值有一个谜题值得探讨:

>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):
  File "<ipython-input-53-d877fb0e9d36>", line 1, in <module>
    t[2] += [50, 60]
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
  • 不要把可变对象放在元组里
  • 增量赋值不是一个原子操作
  • 可使用dis.dis(sentence)查看语句后的字节码

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

list.sort 会就地排序列表,返回值为None

sorted可接受任一可迭代对象作为参数,同时新建一个列表作为返回值。

这两个函数都有两个可选的关键字参数 reversekey

2.9 用bisect来管理已排序的序列

bisect模块主要包含两个主要函数,bisectinsort, 两个函数都利用二分查找算法在有序序列中查找或者插入元素。

2.10 列表并非是首选

数组

当我们需要一个只包含数字的列表时,array.array 会比 list 更高效。 因为数组背后存的不是Python中的float对象,而是字节表示。

内存视图

memoryview 是一个内置类,可以让用户在不复制内容的情况下操作同一个数组的不同切片。个人感觉是内存中的内容不变,改变读取它的方式(如以无符号整型读取或者以有符号整型读取),读取的结果完全不同。

NumPy和SciPy

无需多言。

双向队列和其他形式的队列

collections.dequequeuemultiprocessing

本文只是对书中的主要内容做了小结,对于不常用到的内容只是稍微提及,以后需要用到的时候再查。若想更详细地了解,还是强烈推荐看原书,书中的示例代码尽显Pythonic,读起来酣畅淋漓,醍醐灌顶。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值