Python用统一的风格去处理序列数据。不管是哪种数据结构,字符串、列表、字节序列、数组、XML元素,抑或是数据库查询结果,它们都共用一套丰富的操作:迭代、切片、排序、拼接。
2.1 列表推导和生成器表达式
列表推导是构建列表的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。
2.1.1列表推导和可读性
示例2-1 把一个字符串变成Unideode码位的列表
>>> symbols = '&^%$#@'
>>> codes = []
>>> for symbol in symbols:
... codes.append(ord(symbol))
...
>>> codes
[38, 94, 37, 36, 35, 64]
示例2-2 将上面的示例改成列表推导
>>> symbols = '&^%$#@'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[38, 94, 37, 36, 35, 64]
示例2-2读起来更方便,因为这段代码的功能从字面上就能轻松地看出来。
2.1.2 列表推导同filter和map的比较
示例2-3 用列表推导和map/filter的组合来创建同样的列表
>>> symbols = '&^%$#@'
>>> beyonds = [ord(s) for s in symbols if ord(s) > 40]
>>> beyonds
[94, 64]
>>> beyonds = list(filter(lambda c: c > 40, map(ord, symbols)))
>>> beyonds
[94, 64]
2.1.3 笛卡尔积
用列表推导可以生成两个或两个以上的可迭代类型的笛卡尔积。
示例2-4 使用列表推导计算笛卡尔积
>>> 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')
列表推导中的多个for循环与for循环的嵌套类似,列表推导中写在前面的for类似于外层for,后面的是内层。
列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。
2.1.4 生成器表达式
虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。这是因为生成器表达式可以逐个地 产生元素,显然这种方式更节省内存。
生成器表达式的语法跟列表推导差不多,只不过把方括号改成圆括号而已。
示例2-5 用生成器表达式初始化元组和数组
>>> symbols = '&^%$#@'
>>> tuple(ord(s) for s in symbols)
(38, 94, 37, 36, 35, 64)
>>> import array
>>> array.array('I',(ord(s) for s in symbols))
array('I', [38, 94, 37, 36, 35, 64])
示例2-6 使用生成器表达式计算笛卡尔积
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ((c,s) for c in colors for s in sizes):
... print(tshirt)
...
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')
2.2 元祖不仅仅是不可变的列表
元组除了用作不可变列表,它还可以用于没有字段名的记录。
2.2.1 元祖和记录
元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
示例2-7 把元组用作记录
>>> city, year, pop, chg, area = ('tokyo', 2003, 32450, 0.66, 8014)
>>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
>>> for country, _ in traveler_ids:
... print(country)
...
USA
BRA
ESP
for循环可以分别提取元组里的元素,也叫做拆包(unpacking).
2.2.2 元祖拆包
最好辨认的元组拆包形式就是平行赋值,也就是把一个可迭代对象里的元素,一并赋值到由对应的变量组成的元组中。
>>> lax_coordinates = (33.9425, -118.408056)
>>> lattitude, longitude = lax_coordinates
>>> lattitude
33.9425
>>> longitude
-118.408056
另外一个很优雅的写法当属不使用中间变量交换两个变量的值:
>>> a, b = b, a
还可以用*运算符把一个可迭代对象拆开作为函数的参数:
>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
>>> quotient, remainder = divmod(*t)
>>> quotient, remainder
(2, 4)
用*来处理剩下的元素
在Python中,函数用*args来获取不确定数量的参数算是一种经典写法了。 于是Python3里,这个概念被扩展到了平行赋值中:
>>> a, b, *rest = range(5)
>>> a, b, *rest
(0, 1, 2, 3, 4)
>>> 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, [])
在平行赋值中,*前缀只能用在一个变量名前面,但是这个变量可以出现在赋值表达式的任意位置:
>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
>>> *head, b, c, d = range(5)
>>> head, b, c, d
([0, 1], 2, 3, 4)
2.2.3 嵌套元祖拆包
示例2-8 用嵌套来获取经度
>>> metro_areas = [ ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),]
>>> for name, cc, pop , (latitude, longitude) in metro_areas:
... if longitude >= 0:
... print(name, ' ', latitude, ' ', longitude)
...
Tokyo 35.689722 139.691667
Delhi NCR 28.613889 77.208889
2.2.4 具名元组
collections.namedtuple是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类。 示例2-9 定义和使用具名元组
>>> 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'
除了从普通元组那继承来的属性外,具名元组还有一些自己专有的属性。其中几个最有用的: _fields类属性 、类方法_make(iterable)和实例方法_asdict()
示例2-10 具名元组的属性和方法 (接续前一个示例)
>>> City._fields
('name', 'country', 'population', 'coordinates')
>>> delhi_data = ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
>>> delhi = City._make(delhi_data)
>>> delhi
City(name='Delhi NCR', country='IN', population=21.935, coordinates=(28.613889, 77.208889))
>>> delhi._asdict()
OrderedDict([('name', 'Delhi NCR'), ('country', 'IN'), ('population', 21.935), ('coordinates', (28.613889, 77.208889))])
>>> for key, value in delhi._asdict().items():
... print(key + ':', value)
...
name: Delhi NCR
country: IN
population: 21.935
coordinates: (28.613889, 77.208889)
2.2.5 作为不可变列表的元组
如果把元组当做列表来用的话,最好先了解一下它们的相似度如何。除了跟增减元素相关的方法外,元祖支持列表的其他所有方法。