使用Python的列表理解符号

商业

在 Python 中,在生成一个新的列表时,使用列表理解符号很简单。(List comprehensions)

在这篇文章中,我们将首先讨论以下内容

  • 列表理解符号的基本类型
  • 带有条件分支的列表理解符号,通过if来实现
  • 与三元运算符的组合(类似if else的处理)。
  • zip(),enumerate()与这些结合起来
  • 嵌套列表包含符号

接下来,我们将用示例代码解释一套列表理解的符号。

  • 集合符号(Set comprehensions)
  • 词典收录符号(Dict comprehensions)
  • 发电机类型(Generator expressions)

列表理解符号的基本类型

列表理解的符号写法如下。

[Expression for Any Variable Name in Iterable Object]

它通过一个任意的变量名获取一个可迭代对象的每个元素,如列表、元组或范围,并用一个表达式对其进行评估。一个以评估结果为元素的新列表被返回。

这里给出了一个例子,以及一个等价的for语句。

squares = [i**2 for i in range(5)]
print(squares)
# [0, 1, 4, 9, 16]
squares = []
for i in range(5):
    squares.append(i**2)

print(squares)
# [0, 1, 4, 9, 16]

同样的过程也可以用map()来完成,但由于其简单明了,我们更喜欢用列表理解的符号。

带有条件分支的列表理解符号,通过if来实现

用if进行条件性分支也是可以的。将if写在后缀中,如下所示。

[Expression for Any Variable Name in Iterable Object if Conditional Expression]

只有条件表达式为真的可迭代对象的元素被表达式评估,并返回一个新的列表,其元素是结果。

你可以在条件表达式中使用任何变量名称。

这里给出了一个例子,以及一个等价的for语句。

odds = [i for i in range(10) if i % 2 == 1]
print(odds)
# [1, 3, 5, 7, 9]
odds = []
for i in range(10):
    if i % 2 == 1:
        odds.append(i)

print(odds)
# [1, 3, 5, 7, 9]

同样的过程也可以用filter()来完成,但由于其简单明了,我们更喜欢用列表理解的符号。

与三元运算符的组合(类似if else的处理)。

在上面的例子中,只有那些符合标准的元素被处理,而那些不符合标准的元素被排除在新的列表之外。

如果你想根据条件切换处理,或者你想对不满足条件的元素进行不同的处理,如if else,请使用三元运算符。

在Python中,三元运算符可以写成如下形式

Value When True if Conditional Expression else Value When False

这被用于列表理解符号的表达部分,如下所示。

[Value When True if Conditional Expression else Value When False for Any Variable Name in Iterable Object]

这里给出了一个例子,以及一个等价的for语句。

odd_even = ['odd' if i % 2 == 1 else 'even' for i in range(10)]
print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
odd_even = []
for i in range(10):
    if i % 2 == 1:
        odd_even.append('odd')
    else:
        odd_even.append('even')

print(odd_even)
# ['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']

也可以用任意的变量名来写表达式的真和假值。

如果条件得到满足,就会进行一些处理,否则,原始可迭代对象的值将保持不变。

odd10 = [i * 10 if i % 2 == 1 else i for i in range(10)]
print(odd10)
# [0, 10, 2, 30, 4, 50, 6, 70, 8, 90]

与zip()和enumerate()的组合

经常在for语句中使用的有用的函数包括zip(),它结合了多个迭代变量,以及enumerate(),它返回一个值和它的索引。

当然,也可以用列表理解符号来使用zip()和enumerate()。这不是一种特殊的语法,如果你考虑到与for语句的对应关系,这并不困难。

zip()的例子。

l_str1 = ['a', 'b', 'c']
l_str2 = ['x', 'y', 'z']

l_zip = [(s1, s2) for s1, s2 in zip(l_str1, l_str2)]
print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]
l_zip = []
for s1, s2 in zip(l_str1, l_str2):
    l_zip.append((s1, s2))

print(l_zip)
# [('a', 'x'), ('b', 'y'), ('c', 'z')]

enumerate()的例子。

l_enu = [(i, s) for i, s in enumerate(l_str1)]
print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]
l_enu = []
for i, s in enumerate(l_str1):
    l_enu.append((i, s))

print(l_enu)
# [(0, 'a'), (1, 'b'), (2, 'c')]

在使用if时,其思路与之前相同。

l_zip_if = [(s1, s2) for s1, s2 in zip(l_str1, l_str2) if s1 != 'b']
print(l_zip_if)
# [('a', 'x'), ('c', 'z')]

每个元素也可以用来计算一个新元素。

l_int1 = [1, 2, 3]
l_int2 = [10, 20, 30]

l_sub = [i2 - i1 for i1, i2 in zip(l_int1, l_int2)]
print(l_sub)
# [9, 18, 27]

嵌套列表包含符号

像嵌套for循环一样,列表理解符号也可以被嵌套。

[Expression for Variable Name 1 in Iterable Object 1
    for Variable Name 2 in Iterable Object 2
        for Variable Name 3 in Iterable Object 3 ... ]

为方便起见,增加了换行和缩进,但对语法来说不是必需的;它们可以在一行中继续。

这里给出了一个例子,以及一个等价的for语句。

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

flat = [x for row in matrix for x in row]
print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
flat = []
for row in matrix:
    for x in row:
        flat.append(x)

print(flat)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

也可以使用多个变量。

cells = [(row, col) for row in range(3) for col in range(2)]
print(cells)
# [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]

你也可以做条件性分支。

cells = [(row, col) for row in range(3)
         for col in range(2) if col == row]
print(cells)
# [(0, 0), (1, 1)]

也可以对每个可迭代对象进行条件性分支。

cells = [(row, col) for row in range(3) if row % 2 == 0
         for col in range(2) if col % 2 == 0]
print(cells)
# [(0, 0), (2, 0)]

集合符号(Set comprehensions)

将列表理解符号中的方括号[]改为大括号{},可以创建一个集合(集合型对象)。

{Expression for Any Variable Name in Iterable Object}
s = {i**2 for i in range(5)}

print(s)
# {0, 1, 4, 9, 16}

词典收录符号(Dict comprehensions)

词典(dict类型的对象)也可以用理解性符号生成。

{},并在表达式部分指定键和值为键:值。

{Key: Value for Any Variable Name in Iterable Object}

可以为键和值指定任何表达式。

l = ['Alice', 'Bob', 'Charlie']

d = {s: len(s) for s in l}
print(d)
# {'Alice': 5, 'Bob': 3, 'Charlie': 7}

要从一个键和值的列表中创建一个新的字典,使用 zip() 函数。

keys = ['k1', 'k2', 'k3']
values = [1, 2, 3]

d = {k: v for k, v in zip(keys, values)}
print(d)
# {'k1': 1, 'k2': 2, 'k3': 3}

发电机类型(Generator expressions)

如果列表理解符号中的方括号[]被用作圆括号(),则会返回一个生成器,而不是一个元组。这就是所谓的生成器表达式。

列表理解符号的例子。

l = [i**2 for i in range(5)]

print(l)
# [0, 1, 4, 9, 16]

print(type(l))
# <class 'list'>

生成器表达式的例子。如果你打印()这个生成器,它不会打印出它的内容,但如果你用for语句运行它,你可以得到内容。

g = (i**2 for i in range(5))

print(g)
# <generator object <genexpr> at 0x10af944f8>

print(type(g))
# <class 'generator'>

for i in g:
    print(i)
# 0
# 1
# 4
# 9
# 16

生成器表达式还允许使用if以及列表理解符号进行条件分支和嵌套。

g_cells = ((row, col) for row in range(0, 3)
           for col in range(0, 2) if col == row)

print(type(g_cells))
# <class 'generator'>

for i in g_cells:
    print(i)
# (0, 0)
# (1, 1)

例如,如果使用列表理解符号生成一个有大量元素的列表,然后用for语句进行循环,如果使用列表理解符号,包含所有元素的列表将在开始时生成。另一方面,如果使用生成器表达式,每次重复循环时,都会逐一生成元素,从而减少内存的使用量。

如果生成器表达式是函数的唯一参数,圆括号()可以省略。

print(sum([i**2 for i in range(5)]))
# 30

print(sum((i**2 for i in range(5))))
# 30

print(sum(i**2 for i in range(5)))
# 30

至于处理速度,在处理所有元素时,列表理解符号往往比生成器符号快。

然而,当用all()或any()进行判断时,例如,当出现false或true时,就会确定结果,所以使用生成器表达式可能比使用列表理解符号要快。

没有元组的理解符号,但是如果你使用一个生成器表达式作为tuple()的参数,你可以生成一个理解符号的元组。

t = tuple(i**2 for i in range(5))

print(t)
# (0, 1, 4, 9, 16)

print(type(t))
# <class 'tuple'>