Python 列表理解:你理解吗?!?

举报
Tiamo_T 发表于 2021/10/01 20:30:14 2021/10/01
2.8k+ 0 0
【摘要】 你听说过 Python 中的列表推导式吗?它简化了您使用列表的方式,并使您的代码更加简洁。列表推导式是一种 Python 构造,可减少生成新列表或过滤现有列表所需的代码行。列表推导式包含在方括号内,它由一个表达式、一个或多个 for 循环和一个用于过滤生成的列表的可选条件组成。我们将首先定义列表理解,然后通过一系列示例使这部分成为您的编码知识。列表理解有什么作用?Python列表内涵允许创建...

你听说过 Python 中的列表推导式吗?它简化了您使用列表的方式,并使您的代码更加简洁。

列表推导式是一种 Python 构造,可减少生成新列表或过滤现有列表所需的代码行。列表推导式包含在方括号内,它由一个表达式、一个或多个 for 循环和一个用于过滤生成的列表的可选条件组成。

我们将首先定义列表理解,然后通过一系列示例使这部分成为您的编码知识。

列表理解有什么作用?

Python列表内涵允许创建一个全新的列表,或生成列表通过过滤或映射现有列表。

列表推导式使用以下语法:

new_list = [expression(item) for item in iterable if condition]

Python 中可迭代对象的示例是列表元组、集合和字符串。

给定一个可迭代对象,列表推导会循环遍历该可迭代对象中的项目,将表达式应用于它们中的每一个,并在此基础上生成一个新列表。

还可以指定一个可选条件来过滤迭代中的项目。

列表推导的结果是一个新列表,如果您必须使用标准 for 循环和 if 语句创建它,则需要更多的代码行。

下面是没有列表理解的上面一行代码的样子:

new_list = []

for item in iterable:
    if condition:
        new_list.append(expression(item))

在一条线上好多了!

之所以这样称呼列表推导式,是因为它是在 Python 中描述序列的一种全面或完整的方式。

如何使用列表理解创建新列表

使用列表推导式可以做的主要事情之一是创建一个新列表。

例如,让我们看看如何使用列表推导式中的 range 函数创建一个新列表。

>>> numbers = [x for x in range(10)]
>>> print(numbers)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

在这种情况下,列表推导式的表达非常简单,它只是 x。

让我们更新表达式以将 x 的值加倍:

>>> numbers = [2*x for x in range(10)]
>>> print(numbers)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

如您所见,我们创建了一个列表,其中每个元素都乘以 2。

表达式可以是您想要的任何内容。

如何向列表推导式添加单个条件语句

让我们从上一节中的列表理解开始。

要了解有关列表推导式的更多信息,下一步是为其添加条件。

我们将使用的语法是:

new_list = [expression(item) for item in iterable if condition]

例如,假设我们仍要生成以下列表:

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

但是这次我们要排除大于或等于 5 的数字。

>>> numbers = [2*x for x in range(10) if x < 5]
>>> print(numbers)
[0, 2, 4, 6, 8]

我们使用条件过滤了新列表中的元素。

如何向列表推导式添加两个条件

要将两个条件添加到列表推导式中,只需将这两个条件一个接一个地添加到列表推导式的末尾(在关闭方括号之前)。

更新之前的列表推导式以仅考虑 2 到 5 之间的数字(不包括 2 和 5)。

>>> numbers = [2*x for x in range(10) if x > 2 and x < 5]
>>> print(numbers)
[6, 8]

说得通?

如何将 For 循环转换为列表理解?

让我们从定义一个字符串列表开始:

animals = ['tiger', 'lion', 'elephant']

我想创建一个for 循环,在每个字符串的末尾添加字母 's' 以创建复数列表。

>>> new_animals = []
>>> for animal in animals:
...     new_animals.append(animal + 's')
... 
>>> print(new_animals)
['tigers', 'lions', 'elephants']

首先,我们定义一个新的空列表,用于复数名词。然后在 for 循环的每次迭代中,我们使用 append 方法向新列表添加一个字符串。

这段代码有效,但有没有办法让它更简洁?

使用列表推导式,我们可以简化此代码,方法如下:

>>> new_animals = [animal + 's' for animal in animals]
>>> print(new_animals)
['tigers', 'lions', 'elephants']

我们用一行代码创建了一个新列表,而不是像我们之前看到的那样使用三行代码。

使用列表推导式时,我们不必在开始时创建一个空列表。

在这个列表推导式中,表达式animal + 's',它后面跟着一个for循环,它遍历初始列表1的元素,并将表达式应用于每个元素。

你能在列表理解中使用 Else 吗?

在前面的示例中,我们在列表推导式中使用了 if 语句。

但是,您是否还可以使用 else 语句向列表推导式添加多个条件?

试一试吧…

...从下面的代码开始:

>>> numbers = [2*x for x in range(10) if x > 2 and x < 5]

要添加 else 条件,我们必须重新排列列表理解元素的顺序。

我们必须将条件移动到 for 关键字之前,以便在不满足 if 条件时返回一个不同于 2*x 的值。

>>> numbers = [2*x if x > 2 and x < 5 else 3*x for x in range(10)]
>>> print(numbers)
[0, 3, 6, 6, 8, 15, 18, 21, 24, 27]

所以,这是这段代码中发生的事情……

如果 x 的值介于 2 和 5 之间,则列表推导式返回 2*x,否则返回 3*x。

例如,数字 1 不在 2 和 5 之间,因此结果是 3*1 = 3。

在列表理解中使用 Elif

无法在列表推导式中使用 elif 语句,但可以通过使用多个 else 语句来实现相同的行为。

从以下代码开始:

>>> numbers = [2*x if x > 2 and x < 5 else 3*x for x in range(10)]

目前情况如下:

  • 如果 x > 2 且 x < 5 => 返回 2*x
  • 否则 => 返回 3*x

我想实现以下行为:

  • 如果 x > 2 且 x < 5 => 返回 2*x
  • 否则如果 x <=2 => 返回 3*x
  • 否则 => 返回 4*x

列表推导式中的条件表达式基于三元运算符,我们可以使用以下代码来实现我们想要的行为。

>>> numbers = [2*x if x > 2 and x < 5 else 3*x if x <=2 else 4*x for x in range(10)]
>>> print(numbers)
[0, 3, 6, 6, 8, 20, 24, 28, 32, 36]

我知道,这是一个很长的表达式,此时我会考虑使用标准实现而不是列表理解。

编写有效的代码并不是唯一重要的事情……

编写可读代码非常重要,因为如果代码不可读,这可能会导致错误,并使管理现有代码成为一场噩梦。

这个列表推导式也可以写成以下方式(仍然不使用 elif):

numbers = []

for x in range(10):
    if x > 2 and x < 5:
        numbers.append(2*x)
    else:
        if x <=2:
            numbers.append(3*x)
        else:
            numbers.append(4*x)  

如果您打印数字的值,您会得到相同的结果:

[0, 3, 6, 6, 8, 20, 24, 28, 32, 36]

这段代码肯定比列表推导式更具可读性,如果我们使用 elif 语句,它会变得更加可读:

numbers = []

for x in range(10):
    if x > 2 and x < 5:
        numbers.append(2*x)
    elif x <=2:
        numbers.append(3*x)
    else:
        numbers.append(4*x)

在您的计算机上执行此代码并验证结果是否相同。

如何在列表理解中使用 Break 语句

在标准Python for 循环中,如果出现特定条件,您可以使用 break 语句停止循环的执行。

你怎么能用列表理解来做同样的事情?

列表推导式不支持 break 语句,但可以使用替代方法来模拟 break 语句的行为。

例如,假设我们有一个随机数列表,如果遇到特定数字,我们希望停止执行列表解析。

首先让我们看看如何没有列表理解的情况下生成随机数列表:

import random

random_numbers = []
while len(random_numbers) < 10:
    random_number = random.randint(1, 5)
    random_numbers.append(random_number)

我们创建一个空列表,然后向其添加 1 到 5 之间的随机数,直到数字列表有 10 个元素。

这是输出:

[1, 3, 5, 3, 2, 1, 3, 3, 4, 3]

现在让我们添加一个break 语句来在遇到数字 3 时停止 while 循环的执行。

在中断循环之前,我们将把数字 3 添加到列表中。通过这种方式,我们可以通过将数字 3 视为新数字列表的最后一个元素来确认我们程序中的逻辑。

import random

random_numbers = []
while len(random_numbers) < 10:
    random_number = random.randint(1, 5)
    random_numbers.append(random_number)

    if random_number == 3:
        break

该程序运行良好。如果 random.randint 没有生成数字 3,您可能需要多次运行它。

[5, 3]

现在,让我们对列表理解做同样的事情,首先生成 10 个随机数的完整列表......

>>> random_numbers = [random.randint(1,5) for x in range(10)]
>>> print(random_numbers)
[2, 2, 4, 4, 4, 1, 3, 5, 2, 4]

列表理解再次震撼!一行替换多行代码。

现在,如果遇到数字 3,我们如何停止列表理解?

一种可能的方法需要一个外部模块:itertools。我们将使用函数itertools.takewhile()

首先我们生成random_numbers列表。

>>> import itertools
>>> random_numbers = [random.randint(1,5) for x in range(10)]
>>> print(random_numbers)
[2, 3, 5, 4, 5, 4, 2, 5, 3, 4]

然后我们将它传递给函数 itertools.takewhile。

>>> print(itertools.takewhile(lambda number: number !=3, random_numbers))
<itertools.takewhile object at 0x7f88a81fe640>

函数 itertools.takewhile 取为:

  • 第一个参数是一个lambda,它定义了程序继续运行的条件。
  • 第二个参数是可迭代的。

它返回一个 itertools.takewhile 对象,我们必须将其转换为列表才能查看元素。

>>> print(list(itertools.takewhile(lambda number: number !=3, random_numbers)))
[2]

代码做我们想要的。同时,该行为与使用 break 语句的行为并不完全相同。

那是因为我们首先生成完整的随机数列表,然后遍历它们直到遇到数字 3。

同样在第二个实现中,数字 3 未包含在最终列表中。

在这种情况下,使用 break 语句肯定比需要itertools.takewhile和 lambda的复杂列表理解容易得多。

有点过分了!😀

对两个或更多列表使用列表理解

将列表推导式应用于两个或多个列表的一种方法是将它与zip() 函数一起使用。

>>> cities = ['Rome', 'Warsaw', 'London']
>>> countries = ['Italy', 'Poland', 'United Kingdom']
>>> [(city, country) for city, country in zip(cities, countries)]
[('Rome', 'Italy'), ('Warsaw', 'Poland'), ('London', 'United Kingdom')]

与 zip 函数一起使用的列表推导返回一个元组列表,其中第 n 个元组包含每个列表的第 n 个元素。

如果我们将三个列表传递给列表推导式(依此类​​推),这同样适用。

>>> cities = ['Rome', 'Warsaw', 'London']
>>> countries = ['Italy', 'Poland', 'United Kingdom']
>>> languages = ['Italian', 'Polish', 'English']
>>> [(city, country, language) for city, country, language in zip(cities, countries, languages)]
[('Rome', 'Italy', 'Italian'), ('Warsaw', 'Poland', 'Polish'), ('London', 'United Kingdom', 'English')]

用列表理解替换 Map 和 Lambda

map 函数将给定的函数应用于可迭代的元素。

例如,您可以使用 map 函数将列表中每个数字的值加倍。

>>> numbers = [3, 6, 8, 23]
>>> print(map(lambda x: 2*x, numbers))
<map object at 0x7f88a820d2e0>
>>> print(list(map(lambda x: 2*x, numbers)))
[6, 12, 16, 46]

请注意,传递给 map 函数的第一个参数是一个lambda 函数

这是使用列表推导式编写此表达式的方法。

>>> [2*x for x in numbers]
[6, 12, 16, 46]

超级简单!

使用列表推导代替过滤器和 Lambda 函数

使用过滤器功能,您可以根据给定的条件过滤列表的元素。

例如,让我们从前面的数字列表中筛选出小于 10的数字

条件作为第一个参数传递给过滤器函数,并表示为 lambda 函数。

>>> print(filter(lambda x: x<10, numbers))
<filter object at 0x7f88a8202340>
>>> print(list(filter(lambda x: x<10, numbers)))
[3, 6, 8]

现在使用列表推导式编写相同的逻辑。

>>> [x for x in numbers if x < 10]
[3, 6, 8]

用列表理解替换 Reduce 和 Lambda

应用于我们的数字列表的 reduce 函数根据我们使用以下 lambda 函数的事实返回总和:

lambda a,b: a+b

下面是调用reduce函数的结果:

>>> from functools import reduce
>>> numbers = [3, 6, 8, 23]
>>> print(reduce(lambda a,b: a+b, numbers))
40

现在,我们将把它转换成一个列表推导式。为了获得相同的结果,我们还必须使用 sum() 函数。

>>> print(sum([number for number in numbers]))
40

如何使用嵌套列表推导式

嵌套列表推导式在处理列表列表时非常有用。

例如,假设我们要编写代码将矩阵中的每个数字加一。

这是我们的原始矩阵:

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

使用for 循环,我们将执行以下操作:

for row in matrix:
    for index in range(len(row)):
        row[index] += 1

更新后的矩阵为:

[[2, 3, 4], [5, 6, 7], [8, 9, 10]]

我们如何使用列表推导而不是两个嵌套循环?

我们可以尝试将上面的代码简单地转换为列表推导式。

>>> [[row[index] + 1 for index in range(len(row))] for row in matrix]
[[2, 3, 4], [5, 6, 7], [8, 9, 10]]

请注意,我们在另一个列表推导式中使用了一个列表推导式。这就是为什么这些被称为嵌套列表理解

列表理解和生成器表达式之间的区别

与列表推导式非常相似的 Python 构造是生成器表达式。

要将列表推导式转换为生成器表达式,请将方括号替换为圆括号。

让我们看看这如何应用于我们之前使用过的随机数列表。

请记住在运行以下代码之前导入 random 模块,否则您将看到NameError 异常

列表理解

>>> random_numbers = [random.randint(1,5) for x in range(10)]
>>> print(random_numbers)
[1, 4, 3, 5, 3, 4, 5, 4, 5, 4]
>>> print(type(random_numbers))
<class 'list'>

生成器表达式

>>> random_numbers = (random.randint(1,5) for x in range(10))
>>> print(random_numbers)
<generator object <genexpr> at 0x7fccb814e3c0>
>>> print(type(random_numbers))
<class 'generator'>

正如您在使用列表推导式时所见,我们可以在生成的列表中打印元素的完整列表。

这同样不适用于仅返回生成器对象的生成器表达式。

要从生成器对象中获取下一项,我们必须使用Python next 函数

>>> print(next(random_numbers))
3
>>> print(next(random_numbers))
2

列表推导式和生成器表达式之间的主要区别在于它们在内存中存储数据的方式。列表推导式立即返回完整的数字列表。生成器表达式创建一个生成器,该生成器每次返回一个数字,从而优化内存使用。

For 循环与列表理解:速度比较

在本教程的最后一部分中,我想在处理相同数字时对 for 循环和列表理解之间进行速度比较。

使用以下代码创建一个名为for_loop_vs_list_comprehension.py的 Python 文件:

def get_numbers_using_for_loop():
    numbers = []

    for x in range(10):
        numbers.append(2*x)

    return numbers
def get_numbers_using_list_comprehension():
    numbers = [2*x for x in range(10)]
    return numbers

并确认两个函数返回相同的结果:

print(get_numbers_using_for_loop())
print(get_numbers_using_list_comprehension())

[output]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

然后使用timeit模块测量两个函数的速度:

$ python -m timeit -s "from for_loop_vs_list_comprehension import get_numbers_using_for_loop" "get_numbers_using_for_loop()"
500000 loops, best of 5: 868 nsec per loop

$ python -m timeit -s "from for_loop_vs_list_comprehension import get_numbers_using_list_comprehension" "get_numbers_using_list_comprehension()"
500000 loops, best of 5: 731 nsec per loop
使用列表推导式的实现比使用 for 循环的实现更快。

结论

我们已经学到了很多关于 Python 列表理解的知识!

列表推导式如何使您的代码更加简洁,以及它如何替换基于 for 循环、lambda 和 map/reduce/filter 函数的多个 Python 构造,这真是太棒了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。