"list comprehension" 之类的是什么意思?它是如何工作的,我该如何使用它?

What does "list comprehension" and similar mean? How does it work and how can I use it?

我有以下代码:

[x ** 2 for x in range(10)]

当我运行它在Pythonshell时,它returns:

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

我搜索了一下,这似乎叫做 列表推导式 并且类似地似乎有 set/dict 推导式和生成器表达式。但它是如何工作的?

From the documentation:

List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.


关于您的问题,列表理解与以下 "plain" Python 代码做同样的事情:

>>> l = [] 
>>> for x in range(10):
...     l.append(x**2)
>>> l
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

如何写成一行?嗯...我们可以...可能...使用 map() with lambda:

>>> list(map(lambda x: x**2, range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

但是仅仅使用列表理解不是更清楚和更简单吗?

>>> [x**2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

基本上,我们可以用 x 做任何事情。不仅x**2。例如,运行一个方法x:

>>> [x.strip() for x in ('foo\n', 'bar\n', 'baz\n')]
['foo', 'bar', 'baz']

或者使用x作为另一个函数的参数:

>>> [int(x) for x in ('1', '2', '3')]
[1, 2, 3]

我们也可以,例如,使用x作为dict对象的键。让我们看看:

>>> d = {'foo': '10', 'bar': '20', 'baz': '30'}
>>> [d[x] for x in ['foo', 'baz']]
['10', '30']

组合怎么样?

>>> d = {'foo': '10', 'bar': '20', 'baz': '30'}
>>> [int(d[x].rstrip('0')) for x in ['foo', 'baz']]
[1, 3]

以此类推


您还可以在列表理解中使用 ifif...else。例如,您只需要 range(10) 中的奇数。你可以这样做:

>>> l = []
>>> for x in range(10):
...     if x%2:
...         l.append(x)
>>> l
[1, 3, 5, 7, 9]

啊,太复杂了。下面这个版本呢?

>>> [x for x in range(10) if x%2]
[1, 3, 5, 7, 9]

要使用if...else三元表达式,需要将if ... else ...放在x之后,而不是放在range(10)之后:

>>> [i if i%2 != 0 else None for i in range(10)]
[None, 1, None, 3, None, 5, None, 7, None, 9]

你听说过nested list comprehension吗?您可以在一个列表理解 中放置两个或多个for。例如:

>>> [i for x in [[1, 2, 3], [4, 5, 6]] for i in x]
[1, 2, 3, 4, 5, 6]

>>> [j for x in [[[1, 2], [3]], [[4, 5], [6]]] for i in x for j in i]
[1, 2, 3, 4, 5, 6]

让我们谈谈第一部分,for x in [[1, 2, 3], [4, 5, 6]] 给出了 [1, 2, 3][4, 5, 6]。然后,for i in x 给出 123456

警告:你总是需要把for x in [[1, 2, 3], [4, 5, 6]] 放在之前 for i in x:

>>> [j for j in x for x in [[1, 2, 3], [4, 5, 6]]]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
NameError: name 'x' is not defined

我们还有集合理解字典理解生成器表达式

set comprehensions和list comprehensions基本相同,只是前者returns一个set而不是一个列表:

>>> {x for x in [1, 1, 2, 3, 3, 1]}
{1, 2, 3}

等于:

>>> set([i for i in [1, 1, 2, 3, 3, 1]])
{1, 2, 3}

一个dict comprehension看起来像一个集合推导式,但它使用{key: value for key, value in ...}{i: i for i in ...}而不是{i for i in ...}

例如:

>>> {i: i**2 for i in range(5)}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

它等于:

>>> d = {}
>>> for i in range(5):
...     d[i] = i**2
>>> d
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

(i for i in range(5))是否给出一个元组?不!是 generator expression。哪个returns一个生成器:

>>> (i for i in range(5))
<generator object <genexpr> at 0x7f52703fbca8>

等于:

>>> def gen():
...     for i in range(5):
...         yield i
>>> gen()
<generator object gen at 0x7f5270380db0>

并且您可以将其用作生成器:

>>> gen = (i for i in range(5))
>>> next(gen)
0
>>> next(gen)
1
>>> list(gen)
[2, 3, 4]
>>> next(gen)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

注意:如果在函数中使用列表理解,则不需要[]函数可以遍历生成器。例如,sum():

>>> sum(i**2 for i in range(5))
30

相关(关于生成器):Understanding Generators in Python.

有列表、字典和集合推导,但没有元组推导(尽管探索 "generator expressions")。

它们解决了 Python 中的传统循环是语句(不是 return 任何东西)而不是 return 值的表达式的问题。

它们不是所有问题的解决方案,可以重写为传统循环。当需要在迭代之间维护和更新状态时,它们会变得很尴尬。

它们通常包括:

[<output expr> <loop expr <input expr>> <optional predicate expr>]

但可以以许多有趣和奇怪的方式进行扭曲。

它们可以类似于传统的map()filter()操作,这些操作在Python中仍然存在并继续使用。

如果做得好,他们会有很高的满意度。

如果您更喜欢以更直观的方式了解正在发生的事情,那么这可能会有所帮助:

# for the example in the question...

y = []
for x in range(10):
    y += [x**2]

# is equivalent to...

y = [x**2 for x in range(10)]

# for a slightly more complex example, it is useful
# to visualize  where the various x's end up...

a = [1,2,3,4]
b = [3,4,5,6]
c = []

for x in a:
          if x in b:
                  c += [x]
#   \         \        /
#    \    _____\______/
#     \  /      \
#      \/        \
#      /\         \
#     /  \         \
#    /    \         \
c = [x for x in a if x in b]

print(c)

...产生输出 [3, 4]

我最近(在其他 SO 问题上和同事那里)看到很多关于列表理解如何工作的困惑。一点点数学教育可以帮助理解为什么语法是这样的,以及列表理解的真正含义。

语法

最好将列表理解视为 set/collection 上的谓词,就像我们在数学中使用集合生成器符号一样。这个符号对我来说实际上感觉很自然,因为我拥有数学本科学位。但是忘了我吧,Guido van Rossum(Python 的发明者)拥有数学硕士学位并有数学背景。

设置构建器符号速成课程

以下是集合生成器符号工作原理的(非常基础的):

所以,这个集合构建器表示法表示一组严格为正的数字(即 [1,2,3,4,...])。

混淆点

1) 集合生成器表示法中的谓词过滤器仅指定我们要保留哪些项目,列表理解谓词做同样的事情。您不必包含特殊的省略项的逻辑,除非被谓词包含,否则它们将被省略。空谓词(即末尾没有条件)包括给定集合中的所有项目。

2) set builder notation 中的 predicate filter 放在最后,在 list comprehensions 中也类似。 (some) 初学者认为像 [x < 5 for x in range(10)] 这样的东西会给他们列表 [0,1,2,3,4],而实际上它输出 [True, True, True, True, True, False, False, False, False, False]。我们得到输出 [True, True, True, True, True, False, False, False, False, False] 是因为我们要求 Python 为 range(10) 中的 all 项目评估 x < 5。没有谓词意味着我们从集合中得到所有东西(就像在集合构建器符号中一样)。

如果您在使用列表推导式时牢记集合构建器符号,它们会更容易被接受。

HTH!

简介

A list comprehension is a high level, declarative way to create a list in Python. The main benefits of comprehensions are readability and maintainability. A lot of people find them very readable, and even developers who have never seen them before can usually guess correctly what it means.

# Snippet 1
squares = [n ** 2 for n in range(5)]

# Snippet 2
squares = []
for n in range(5):
    squares.append(n ** 2)

两个代码片段都会生成 squares 等于 [0, 1, 4, 9, 16]

请注意,在第一个片段中,您键入的是声明您想要的列表类型,而第二个片段是指定如何创建它。这就是为什么理解是高级和声明性的。

语法

[EXPRESSION for VARIABLE in SEQUENCE]

EXPRESSION 是任何 Python 表达式,但通常在其中包含一些变量。此变量在 VARIABLE 字段中说明。 SEQUENCE 定义变量枚举的值的来源。

考虑片段 1,[n ** 2 for n in range(5)]

  • EXPRESSIONn ** 2
  • VARIABLEn
  • SEQUENCErange(5)

请注意,如果您检查 squares 的类型,您会发现列表理解只是一个常规列表:

>>> type(squares)
<class 'list'>

关于表达式的更多信息

表达式可以是任何可以归结为一个值的东西:

  • 算术表达式如n ** 2 + 3 * n + 1
  • f(n) 这样的函数调用,使用 n 作为变量
  • s[::-1]
  • 这样的切片操作
  • 方法调用 bar.foo()
  • ...

一些例子:

>>> [2 * x + 3 for x in range(5)]
[3, 5, 7, 9, 11]
>>> [abs(num) for num in range(-5, 5)]
[5, 4, 3, 2, 1, 0, 1, 2, 3, 4]
>>> animals = ['dog', 'cat', 'lion', 'tiger']
>>> [animal.upper() for animal in animals]
['DOG', 'CAT', 'LION', 'TIGER']

过滤:

最终列表中元素的顺序由SEQUENCE的顺序决定。但是,您可以过滤掉添加 if 子句的元素:

[EXPRESSION for VARIABLE in SEQUENCE if CONDITION]

CONDITION 是一个计算结果为 TrueFalse 的表达式。从技术上讲,条件不必依赖于 VARIABLE,但通常会使用它。

示例:

>>> [n ** 2 for n in range(5) if n % 2 == 0]
[0, 4, 16]
>>> animals = ['dog', 'cat', 'lion', 'tiger']
>>> [animal for animal in animals if len(animal) == 3]
['dog', 'cat']

此外,请记住 Python 允许您编写除列表以外的其他类型的理解:

  • 词典理解
  • 设置理解