本文我们详细介绍一下 Python 中现有的全部序列类型以及一些较为高级的用法。
以上这些序列中存储的是对象的引用,因此他们不关心所引用的存储对象的类型,也就是说,在一个序列中可以放入不同类型的对象。
上述这些序列类型存储的是对象的值,他们是一段连续的存储空间,只能容纳一种类型。
下面的代码把一个字符串转换成 unicode 码存储在 list 中并输出:
>>> symbols = '$¢£¥€¤'
>>> codes = []
>>> for symbol in symbols:
... codes.append(ord(symbol))
...
>>> codes
[36, 162, 163, 165, 8364, 164]
下面我们将他改成列表推导的形式:
>>> symbols = '$¢£¥€¤'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[36, 162, 163, 165, 8364, 164]
显然,列表推导的方法大大简化了上述代码,逻辑更加清晰简练,他可以十分简洁的实现可迭代类型的元素过滤或加工,并创建出一个新列表。
列表推导中我们是可以放入多个循环的,例如下面这个生成笛卡尔积的例子:
>>> 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')]
但需要注意的是,不要滥用列表推导:
filter 与 map 结合 lambda 表达式也可以做到和列表推导相同的功能,但可读性大为下降。 下面的例子将 Unicode 值大于 127 的字符对应的 Unicode 值加入列表中:
>>> symbols = '$¢£¥€¤'
>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>> beyond_ascii
[162, 163, 165, 8364, 164]
>>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
>>> beyond_ascii
[162, 163, 165, 8364, 164]
上面所有例子中,我们都只生成了列表,如果我们要生成其他类型的序列,列表推导就不适用了,此时生成器表达式成为了更好的选择。 简单地说,把列表推导的方括号变成圆括号就是生成器表达式,但在用法上,生成器表达式通常用于生成序列作为方法的参数。 下面的例子用生成器表达式计算了一组笛卡尔积:
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):
... print(tshirt)
生成器与列表推导存在本质上的不同,生成器实际上是一种惰性实现,他不会一次产生整个序列,而是每次生成一个元素,这与迭代器的原理非常类似,如果列表元素非常多,使用列表生成器可以在很大程度上节约内存的开销。
上一篇文章中,我们介绍了元组作为不可变列表的用法,但一个同样重要的用法是把元组用作信息的记录。
>>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)
可以看到,上面的例子中只用一行代码,就让元组中的每个元素都被赋值给不同的变量,这个过程就被称为元组拆包。
下面就是一个通过元组拆包实现的十分优雅的变量交换操作:
>>> b, a = a, b
除了给变量赋值,只要可迭代对象的元素数与元组中元素数量一致,任何可迭代对象都可以用元组拆包来赋值。
可以用 * 运算符将任何一个可迭代对象拆包作为方法的参数:
>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
Python 允许被拆包赋值的一系列变量中最多存在一个以 开始的变量,他用来接收所有拆包赋值后剩下的变量。args 用来获取不确定参数是最经典的写法了。
>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
元组拆包是可以嵌套的,只要接受元组嵌套结构符合表达式本身的嵌套结构,Python 就可以做出正确的处理。
具名元组就是带有名字和字段名的元组,他用元组模拟了一个简易的类。 我们通过 collections.namedtuple 方法就可以构建一个具名元组:
>>> 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))
本质上,具名元组仍然是元组用于记录元素的一种用法。
除了所有元组具有的属性和方法,具名元组还具有下面三个有用的属性和方法。
序列类型有很多,虽然大部分人在大部分时间都喜欢使用 list,但要知道某些时候你还有更好的选择:
《流畅的 python》。