1 概念解释

列表推导是构建列表的快捷方式,生成器表达式可以用来创建其他任何类型的序列。

列表推导:放在方括号里的表达式,使用关键字for与in,通过处理和过滤一个或多个可迭代对象里的元素构建列表。

生成器:使用生成器函数或生成器表达式构建的迭代器,无需迭代集合就可能生成值。生成斐波那契数列的生成器是个典型实例,这个数列是一种无穷序列。

生成器表达式:放在括号里的表达式,句法与列表推导一样,只是返回的是生成器(而不是列表)


2 列表推导

下面的代码有两部分:前一部分使用通常的做法,即使用for循环构造一个新列表;后者使用的是列表推导构建的新列表。

注:ord(‘a’),a为Unicode字符,返回值为字符a对应的Unicode数值;chr(n),n为Unicode数值,返回值为n对应的Unicode字符。

>>> symbols = '*&^%$#!'
>>> codes = []
>>> for symbol in symbols:
... codes.append(ord(symbol))
...
>>> codes
[42, 38, 94, 37, 36, 35, 33]


>>> symbols = '*&^%$#!'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[42, 38, 94, 37, 36, 35, 33]

比较上面的两段代码,可以看出使用列表推导的代码更短,更易于理解。为了防止滥用列表推导,需要注意:只用列表推导来创建新的列表,而且尽量保持简短。若列表推导的代码超过两行,应该考虑使用for循环重写了。总之,这个度得自己把握好。

列表推导可以帮我们把一个序列或其他可迭代类型中的元素过滤或是加工,然后再新建一个列表。

2.1 列表推导同filter和map的比较

filter和map合起来能做的事情,列表推导也可以做,而且还不需要借助难以理解和阅读的lambda表达式。

下面的代码中,分别用列表推导map/filter组合来创建相同的表单:

>>> symbols = '&^%$#@'
>>> beyond_ascii1 = [ord(s) for s in symbols if ord(s) > 38]
>>> beyond_ascii1
[94, 64]


>>> beyond_ascii2 = list(filter(lambda c: c > 38, map(ord, symbols)))
>>> beyond_ascii2
[94, 64]

2.2 笛卡尔积

笛卡尔积:两个或两个以上的列表的元素对构成元组,这些元组构成的列表就是笛卡尔积。

用列表推导可以生成两个或以上的可迭代类型的笛卡尔积。笛卡尔积是一个列表,列表里的元素是由输入的可迭代类型的元素对构成的元组。笛卡尔积列表的长度等于输入变量的长度的乘积。

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


上图是含有4种花色和3种牌面的列表的笛卡尔积,结果是一个包含12个元素的列表。

这是一个例子:现在需要一个列表,列表里是3种不同尺寸的T恤衫,每个尺寸都有2个颜色,下面的代码用列表推导算出了这个列表,共6种组合:

>>> 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循环。这两部分得到的结果都是先以颜色排列,再以尺码排列。而且两个部分的循环嵌套关系先后顺序都一样。

列表推导的作用只有一个——生成列表。 若想生成其他类型的序列,生成器表达式就派上用场了。


3 生成器表达式

虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素。生成器表达式的语法跟列表推导差不多,只是把方括号换为圆括号。

下面的代码是一个【用生成器表达式建立元组和数组】的例子:

>>> symbols = '*&^%$'
>>> tuple(ord(symbol) for symbol in symbols) # 1
(42, 38, 94, 37, 36)


>>> import array
>>> array.array('I', (ord(symbol) for symbol in symbols)) # 2
array('I', [42, 38, 94, 37, 36])

# 1:若生成器表达式是一个函数调用过程中的唯一参数,则不需要额外再用括号围起来。

# 2:array的构造方法需要两个参数,因此括号是必需的。array构造方法的第一个参数指定了数组中数字的存储方式。

下面的代码利用生成器表达式实现了一个笛卡尔积,用以打印出上文中我们提到过的T恤衫的2种颜色和3种尺码的所有组合。与上文用列表推导的代码相比,用到生成器表达式以后,内存里不会留下一个有6个组合的列表,因为生成器表达式会在每次for循环运行时才生成一个组合。这样的话,可以减少运行开销,如,要计算两个各有1000个元素的列表的笛卡尔积,生成器表达式就可以帮忙省掉运行for循环的可销——即一个含有100万个元素的列表。下面请看代码:

>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes): # 1
... print(tshirt)
...
black S
black M
black L
white S
white M
white L

# 1 生成器表达式逐个产出元素,从来不会一次性产出一个含有6个T恤样式的列表


4 字典推导

字典推导可以从任何以键值对作为元素的可迭代对象中构建出字典。下面的代码是一个例子,展示了利用字典推导可以把一个装满元组的列表编程两个不同的字典:

>>> DIAL_CODES = [(86, 'China'), (91, 'India'), (62, 'Indonesia'), (55, 'Brazil')] # 元组列表
>>> country_code = {country: code for code, country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'Indonesia': 62, 'Brazil': 55}
>>> {code: country.upper() for country, code in country_code.items() if code < 66}
{62: 'INDONESIA', 55: 'BRAZIL'}

5 集合推导

集合推导用于构成一个集合。下面的代码是一个例子:新建一个Latin-1字符集合,该集合里的每个字符的Unicode名字里都有’SIGN’这个单词。代码如下:

>>> from unicodedata import name # 1
>>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i), '')} # 2
{'¥', '¬', '©', '+', 'µ', '$', '°', '>', '®', '#', '¤', '§', '%', '=', '÷', '×', '£', '¶', '¢', '<', '±'}

# 1:从unicodedata模块里导入name函数,用于获取字符的名字

# 2:把【编码在32~255之间的字符】的名字里有’SIGN’单词的挑出来,然后放入一个集合里。