Python 列表推导式:精简编码的艺术
您是否厌倦了编写冗长、重复的代码来操作 Python 中的列表?告别那些挣扎吧!Python 列表推导式是为了挽救这一天。这是一种强大的技术,可让您优雅而简单地转换和过滤列表。在这篇博客中,我们将一起探索列表推导式的世界,从基础知识到高级技术。让我们深入了解 Python 列表推导式的魔力。哦,不要错过最后关于列表推导式的有趣事实!
什么是 Python 中的列表推导式?
Python 中的列表推导式是一种强大的技术,它使我们能够快速简洁地创建新列表。它简化了复杂的操作,提高了代码的可读性。
通过列表推导式,我们可以在一行代码中毫不费力地转换和过滤数据。它从数学集合符号中汲取灵感,并为我们的 Python 编程增添了优雅。
列表推导式背后的数学历史
如上所述,Python 中的列表推导源于数学集合符号。集合生成器符号的概念可以追溯到 19 世纪,当时数学家开始使用它以简洁直观的方式定义集合。
在集合生成器表示法中,集合是通过指定满足特定条件的元素来定义的。例如,我们可以将一组小于 10 的偶数定义为:
{x | x is an even number, x < 10}
这种符号允许我们根据特定条件创建集合,而无需明确列出所有元素。
Python 的列表推导从集合生成器符号中借用了这个想法,并将其应用于列表的创建。我们不是定义一个集合,而是通过表达应该包含的元素和条件来定义一个新列表。列表推导式语法反映了集合生成器表示法,使其直观而优雅。
当我们编写列表推导式时,我们本质上是在描述列表中我们想要的元素以及它们必须满足的任何条件。Python 负责迭代和过滤,为我们提供了一个符合我们标准的新列表。
通过从数学符号中汲取灵感,列表推导式为我们的编程带来了一丝数学上的优雅。它使我们能够从集合和条件的角度思考,使我们能够清晰简洁地表达我们的意图。
Python 列表推导式的语法
现在我们已经了解了列表推导式背后的概念和数学历史,现在让我们深入了解它的语法。
列表推导的语法由三个基本组件组成:表达式、可迭代对象和可选条件。它们共同构成了轻松创建新列表的强大配方。
下面是它的外观:
new_list = [expression for item in iterable if condition]
让我们一步一步地分解它:
expression
:这是我们定义要对可迭代对象中的每个项目执行的操作的部分。它可以是数学运算、转换,甚至是函数调用。我们在这里释放我们的创造力!item
:这表示我们要处理的可迭代对象的每个元素。它可以是我们选择的任何变量名称。iterable
:这是我们从中提取元素的原始列表或序列。它可以是列表、元组、字符串或任何其他可迭代对象。我们甚至可以使用生成器或范围对象作为数据源。condition
(可选):有时,我们只想在新列表中包含满足特定条件的某些元素。该条件允许我们根据逻辑语句过滤元素。如果条件的计算结果为 True,则包含该元素;否则,将跳过它。
从本质上讲,当我们阅读列表推导式时,就像阅读一个句子:“对于可迭代对象中的每个项目,如果满足条件,则执行表达式并将结果包含在新列表中”。
Python 列表推导式示例
现在我们了解了 Python 中列表推导式的语法,让我们深入了解一些展示其真正力量的迷人示例。
示例 1:平方数
假设我们有一个数字列表,我们想对列表中的每个数字进行平方。通过列表推导式,实现这一目标变得非常容易。以下是行动中的魔力:
numbers = [1, 2, 3, 4, 5]
squared = [num ** 2 for num in numbers]
在此示例中,我们使用列表推导来迭代原始列表中的每个数字。我们使用 **
运算符对每个数字进行平方,并将结果存储在一个名为 squared
的新列表中。此代码片段的输出将是:
squared = [1, 4, 9, 16, 25]
太棒了,不是吗?在一行代码中,我们将原始列表转换为包含每个数字的平方值的新列表。
示例 2:从名称中提取首字母缩写
假设我们有一个名称列表,我们想要提取每个名称的首字母。列表推导式使我们能够有效地完成此任务,而无需条件。让我们看看它的实际效果:
names = ["John Doe", "Alice Smith", "Bob Johnson"]
initials = [name.split()[0][0] + name.split()[1][0] for name in names]
在此示例中,我们使用列表推导式遍历列表中的每个名称。在推导式中,我们使用 split()
方法将每个名称拆分为单独的部分。通过访问名字和姓氏的第一个字符,我们获得了首字母。输出将是:
initials = ["JD", "AS", "BJ"]
就这样,我们毫不费力地从每个名字中提取了首字母,使用 Python 中的列表推导式创建了一个充满这些缩写的新列表。
为什么要使用列表推导式?
列表推导式提供了几个令人信服的理由来将其合并到您的 Python 代码中:
- 增强可读性:以简洁易读的方式表达复杂的操作,消除冗长的循环和条件。
- 提高工作效率:用更少的代码行完成更多工作,节省编程任务的时间和精力。
- 提高性能:更快地执行操作,尤其是对于大型数据集或计算密集型任务。
- 用于常见操作的单行代码:使用紧凑直观的单行代码执行常见转换和过滤操作。
- 与函数式编程集成:与函数式编程原则保持一致,增强代码清晰度和模块化。
- 可维护的代码:使您的代码不言自明且更易于维护,从而减少开发人员的认知负担。
什么时候应该使用列表推导式?
现在我们了解了原因,让我们探索列表推导式的地点和时间。这种强大的技术可用于各种方案,以简化代码并使其更具表现力。
- 转换数据:当您需要根据特定条件转换列表中的元素时,请使用列表推导式。它允许您将操作或函数应用于每个元素,使用转换后的数据创建新列表。
- 过滤数据:当您想根据某些条件从列表中提取特定元素时,列表推导式会大放异彩。通过组合迭代语句和条件语句,可以创建仅包含所需元素的新列表。
- 创建集合或字典:列表推导对于构建具有特定规则的集合或字典非常方便。其独特的语法和条件语句使组织数据变得高效和结构化。
- 数据清理:列表推导式简化了数据清理和操作。它使您能够通过对每个元素应用转换来删除不需要的字符、转换数据类型或处理缺失值。
- 列表生成:当您需要生成遵循模式或满足特定要求的列表时,列表推导式是要走的路。它允许您根据公式、序列或其他条件简洁地生成列表。
Python 列表推导中的条件逻辑
Python 中的列表推导不仅允许您迭代可迭代对象并执行操作,而且还使您能够将条件逻辑合并到代码中。是的,你没听错!您可以在创建新列表时添加条件来筛选、转换或操作元素。
让我们深入了解一些有趣的例子,这些例子展示了条件逻辑和列表推导式的神奇组合。
示例 1:筛选偶数
假设您有一个数字列表,并且想要创建一个仅包含偶数的新列表。通过列表推导式,这变得轻而易举。
例:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = [x for x in numbers if x % 2 == 0]
print(even_numbers) # Output: [2, 4, 6, 8, 10]
在一行中,条件语句 if x % 2 == 0
过滤掉所有奇数,留下一个闪闪发光的偶数集合。这有多酷?
示例 2:组合多个条件
列表推导式不会将您限制在单个条件上。您可以释放逻辑运算符的全部功能来创建复杂的过滤条件。
让我们考虑一个示例,其中我们希望根据单词的长度和起始字母过滤单词列表:
words = ['apple', 'banana', 'cherry', 'date', 'elderberry']
filtered_words = [word for word in words if len(word) > 5 and word[0] == 'b']
print(filtered_words) # Output: ['banana']
在此示例中,条件语句 len(word) > 5 and word[0] == 'b'
仅筛选出长度大于 5 并以字母“b”开头的单词。通过组合多个条件,您可以根据您的特定要求精确定制列表推导式。
Python 中的嵌套列表推导式
在 Python 列表推导式领域,有一种强大的技术可以将您的代码提升到下一个优雅和效率的水平:嵌套列表推导式。就像俄罗斯套娃一样,一个娃娃包含较小的娃娃,嵌套列表推导式允许您创建具有多次迭代和条件的复杂列表,所有这些都在一行代码中完成。
让我们探索一些引人入胜的示例,以真正了解嵌套列表推导式的潜力。
示例 1:矩阵转置
考虑这样一种情况:您有一个矩阵(表示为列表列表),并且您想要转置它。传统上,实现此目的将涉及嵌套循环和临时变量。但是通过嵌套列表推导式,魔术以非常简洁的方式展开。
让我们花点时间通过一个例子来惊叹嵌套列表推导式的强大功能:转置矩阵。
matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
print(transposed)
当你第一次遇到这段代码时,你可能会感到一丝惊讶。如此紧凑的表达式如何完成转置矩阵的任务?让我们一起揭开这个谜团!
外部列表推导充当转置矩阵的架构师。它由两个迭代组成:
- 外部迭代
for i in range(len(matrix[0]))
控制转置矩阵的列。它循环遍历原始矩阵中的列数范围。 - 内部迭代
row[i] for row in matrix
在相应的列索引处访问原始矩阵中每一行的元素。
当嵌套列表推导式运行时,它通过从原始矩阵中提取适当的元素并相应地排列它们来动态创建转置矩阵。结果会让你敬畏:
[[1, 4, 7],
[2, 5, 8],
[3, 6, 9]]
但是等等,嵌套列表推导式魔法还有更多!您可以混合和匹配迭代和条件,以实现更复杂的转换。
示例 2:提取元音
假设您有一个句子列表,并且您想从每个句子中提取所有元音。嵌套列表推导式也使您能够优雅地完成此任务:
sentences = ["I love Python",
"Programming is fun",
"Lists are awesome"]
vowels = [char for sentence in sentences for char in sentence if char.lower() in 'aeiou']
print(vowels)
在此示例中,嵌套列表推导执行两次迭代:
- 外部迭代
for sentence in sentences
循环访问sentences
列表中的每个句子。 - 内部迭代
for char in sentence
迭代当前句子中的每个字符。
输出:
['I', 'o', 'e', 'o', 'o', 'a', 'i', 'i', 'u', 'i', 'a', 'e', 'a', 'e', 'o', 'e']
Python 中带有 Zip 的多个可迭代对象
随着我们深入到 Python 列表推导式的世界,我们遇到了一种令人兴奋的技术,它允许我们无缝地组合多个可迭代对象。通过利用 zip
函数的强大功能,我们可以创建从多个源中提取元素的列表推导式。
zip
函数接受两个或多个迭代对象并将相应的元素配对在一起,从而创建元组的迭代器。当我们想要同时处理多个列表时,这使得它非常有用。
让我们深入一个例子来说明在列表推导中使用多个可迭代对象的魔力:
示例 1:合并名字和姓氏
first_names = ['John', 'Emma', 'Michael']
last_names = ['Doe', 'Smith', 'Johnson']
full_names = [first + ' ' + last for first, last in zip(first_names, last_names)]
print(full_names) # Output: ['John Doe', 'Emma Smith', 'Michael Johnson']
在此示例中,我们有两个单独的列表,其中包含名字和姓氏。通过利用 zip()
的幂,我们可以无缝组合两个列表中的相应元素,并创建一个包含全名的新列表 full_names
。
示例 2:计算总销售额
products = ['Apple', 'Banana', 'Orange']
prices = [0.99, 0.25, 0.50]
quantities = [10, 15, 8]
total_sales = [price * quantity for price, quantity in zip(prices, quantities)]
print(total_sales) # Output: [9.9, 3.75, 4.0]
在上面,我们有三个单独的列表,分别代表产品、价格和销售数量。通过使用 zip()
,我们毫不费力地对齐相应的元素并计算每个产品的总销售额。生成的 total_sales
列表将包含价格和数量的乘积,使我们能够跟踪产生的总体收入。
注意:结果列表中的元素数将等于最短可迭代对象的长度。因此,请确保您的可迭代对象长度相等,或相应地处理任何差异。
使用海象运算符在列表推导中分配变量
Python 3.8 中引入的一个令人兴奋的功能是海象运算符,用 “:=” 表示。这个运算符允许我们为列表推导本身中的变量赋值。乍一听可能有点奇特,但它是一个强大的补充,可以使我们的代码更加简洁和富有表现力。
下面是一个示例:
numbers = [10, 20, 30, 40, 50]
doubled_numbers = [num * 2 for num in numbers if (double := num * 2) > 30]
print(doubled_numbers) # Output: [40, 60, 80, 100]
print(double) # Output: 100
在这个例子中,我们有一个数字列表。使用 walrus 运算符,我们将每个数字加倍并将其分配给变量 double
,但前提是加倍值大于 30。
注意:当条件取决于我们要分配的值时,海象运算符特别有用。如果没有海象运算符,我们需要计算两次条件,导致代码效率降低和可读性降低。海象运算符消除了这种重复并简化了我们的逻辑。
在列表推导中使用 Lambda 函数
Lambda 函数,也称为匿名函数,是 Python 编程中紧凑而方便的工具。它们允许我们创建小型的单行函数,而无需正式的函数定义。将 lambda 函数的强大功能与列表推导式相结合可以使我们的代码更加简洁和富有表现力。
让我们探讨一下如何在列表推导中利用 lambda 函数来完成常见任务。
使用 Lambda 函数进行筛选
一个常见的用例是根据某些条件过滤元素。通过 lambda 函数和列表推导式的组合,我们可以在一行代码中实现这一点。
考虑以下示例,其中我们有一个数字列表,我们只想过滤掉偶数:
numbers = [1, 2, 3, 4, 5]
even_numbers = [x for x in numbers if (lambda x: x % 2 == 0)(x)]
print(even_numbers) # Output: [2, 4]
在这个例子中,我们使用 lambda 函数 (lambda x: x % 2 == 0)
作为列表推导式中的条件。此 lambda 函数检查一个数字是否可以被 2 整除,将其指示为偶数。生成的 even_numbers
个列表将仅包含原始列表中的偶数元素。
使用 Lambda 函数进行映射
Lambda 函数对于对列表中的元素执行简单转换也很有用。我们可以将它们与列表推导相结合,以创建一个包含修改元素的新列表。
让我们考虑一个示例,其中我们有一个字符串列表,我们希望使用 lambda 函数将每个字符串大写:
words = ["apple", "banana", "cherry"]
capitalized_words = [(lambda x: x.capitalize())(x) for x in words]
print(capitalized_words) # Output: ['Apple', 'Banana', 'Cherry']
在这种情况下,lambda 函数 (lambda x: x.capitalize())
采用参数 x
并将 capitalize()
方法应用于 words
列表中的每个元素 x
,将第一个字母转换为大写。生成的 capitalized_words
列表将包含修改后的字符串。
Python 中的其他推导式
除了列表推导式之外,Python 还提供了一些更简洁有效的数据操作技术:集合、字典和生成器推导式。让我们进一步探索这些技术!
Python 集合推导式
集合推导式允许您使用简洁的语法在 Python 中创建集合。集合是唯一元素的无序集合,集合推导式提供了一种基于可迭代对象生成集合的便捷方法。下面是一个示例来说明其用法:
numbers = [1, 2, 3, 4, 5]
squared_set = {x**2 for x in numbers}
print(squared_set) # Output: {1, 4, 9, 16, 25}
在这个例子中,我们使用集合推导创建一个名为 squared_set
的集合。它将 numbers
列表中的每个数字平方,并将唯一的平方值存储在集合中。
注: 当您需要从序列中提取唯一元素或执行需要不同值的数学运算时,集合推导特别有用。
Python 字典推导式
字典推导允许您以简洁的方式创建字典,从而提供了一种根据特定条件或转换将键映射到值的便捷方法。下面是一个演示其功能的示例:
names = ['Alice', 'Bob', 'Charlie']
name_lengths = {name: len(name) for name in names}
print(name_lengths) # Output: {'Alice': 5, 'Bob': 3, 'Charlie': 7}
在此示例中,我们使用字典推导式创建一个名为 name_lengths
的字典。它将 names
列表中的每个名称映射到其相应的长度。
字典推导式也可以与条件相结合,有选择地创建字典条目。这允许您在构造字典时过滤和转换数据。这是一种强大的数据操作技术,可以大大简化您的代码。
Python 生成器推导式
当我们深入研究 Python 的推导式时,我们遇到了一个有趣的概念,称为生成器推导式。与列表推导类似,生成器推导式提供了一种简洁有效的方法来在 Python 中创建可迭代对象。然而,有一个关键的区别使生成器推导式与众不同:惰性求值。
生成器推导式允许我们根据需要动态生成值,而不是一次创建整个可迭代对象。在处理大型数据集或内存效率至关重要时,这种惰性评估方法非常有用。
生成器推导式的语法与列表推导式非常相似,但有一个本质区别。我们没有将表达式括在方括号内,而是使用括号。
让我们通过一个简单的例子来探讨这个概念:
squares = (x**2 for x in range(10))
print(squares)
在这个例子中,我们创建了一个生成器推导式,它产生从 0 到 9 的数字平方。请注意使用括号而不是方括号。
输出:
<generator object <genexpr> at 0x7efcdb4ad630>
结果 squares
不是列表,而是一个生成器对象,它充当迭代器。
生成器推导式的显着优势之一是它允许我们迭代元素而无需将它们存储在内存中。在处理大量数据集或无限序列时,此功能变得非常有价值。
要从生成器推导中检索值,我们可以使用 for
循环或 next()
和 list()
等函数。
让我们看看如何在实践中利用生成器推导式:
通过使用 for
循环,我们可以迭代生成器并一次获取一个平方值。
squares = (x**2 for x in range(10))
# Using a for loop to iterate over the generator
for square in squares:
print(square, end=" ")
输出:
0 1 4 9 16 25 36 49 64 81
或者,我们可以使用 next()
函数单独获取值。
# Using next() to retrieve values one at a time
gen = (x**2 for x in range(10))
print(next(gen))
print(next(gen))
输出:
0
1
最后,如果我们需要列表中的所有值,我们可以使用 list()
函数将生成器转换为列表。
# Converting the generator to a list
gen = (x**2 for x in range(10))
squares_list = list(gen)
print(squares_list)
输出:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
元组推导式呢?
虽然 Python 提供了对集合、字典和生成器的推导,但你可能会发现自己想知道是否也支持元组推导式。但是,您可能会惊讶地发现 Python 中并不直接支持元组推导式。但这是为什么呢?
缺乏元组推导式背后的原因在于元组本身的性质。元组是不可变的,这意味着一旦创建元组,它们的值就无法修改。它们充当用于保存对象集合的容器,但它们无法在创建后接收分配或进行任何更改。
另一方面,推导通过迭代项目并将其分配给容器来工作。元组由于不可变性,因此无法实现此赋值过程。因此,元组推导式不能直接在 Python 中实现。
解决方法:创建类似元组推导式的结构
尽管元组推导式不能直接使用,但 Python 提供了实现类似结果的替代方法。一种常见的技术涉及使用生成器表达式,该表达式可用于创建元组。通过将生成器表达式括在括号内,可以生成具有所需值的类似元组的结构。
下面是说明此方法的示例:
# values = (x for x in iterable if condition)
# tuple_result = tuple(values)
numbers = [1, 2, 3, 4, 5]
tuple_result = tuple(x ** 2 for x in numbers)
print(tuple_result) # Output: (1, 4, 9, 16, 25)
在上面的例子中,我们利用生成器表达式根据提供的可迭代对象和条件生成所需的值。然后,我们使用 tuple()
函数将生成器对象转换为元组,从而生成包含计算值的类似元组的结构。
虽然它不是真正的元组推导式,但此解决方法允许您在简洁的代码和高效计算方面实现类似的结果。
Python 列表推导式最佳实践
若要使 Python 列表推导式更有效且更具可读性,请考虑以下最佳做法:
- 保持简单易读:以列表推导式为目标。避免使用复杂的表达式和嵌套结构,以免使代码难以理解。保持清洁,对眼睛方便。
- 使用描述性变量名称:选择传达每个元素用途的有意义的变量名称。这有助于使代码不言自明,并减少对过多注释的需求。
- 注意列表长度:避免在单个推导式中创建过长的列表。冗长的推导式会妨碍可读性,并可能导致性能问题。如果需要,将它们分解成更小的部分。
- 避免复杂的表达:在表现力和复杂性之间取得平衡。避免可能使读者感到困惑的复杂表达。将它们拆分为多个步骤或使用帮助程序函数以明确起见。
- 妥善处理错误案例:考虑潜在的异常或错误。使用条件语句或 try-except 块来处理意外情况。这可确保代码不会崩溃并运行可靠。
- 增量测试和调试:以增量方式测试和调试列表推导式。从较小的数据集或更简单的推导开始,以验证预期结果。逐渐增加复杂性并迭代代码。
- 考虑性能影响:注意列表推导式中的性能影响。涉及大型数据集的嵌套循环或操作可能会影响性能。如有必要,请考虑其他方法。
- 记录意图和目的:记录列表推导式的意图和目的。提供简明扼要的评论,以澄清预期结果以及所做的任何特定条件或假设。
有趣的事实!
你知道 Python 列表推导式的语法从 Haskell 编程语言中汲取灵感吗?是的,这是真的!Python 的创造者在 Haskell 中找到了灵感,Haskell 是一种以其表达能力和简洁语法而闻名的语言。
Haskell 开发于 1990 年,是一种纯函数式编程语言,擅长列表操作和转换。Python 接受了 Haskell 列表推导式的优雅和效率,并将其整合到自己的语法中。
Haskell 中的列表推导式语法:
[ <expression> | <generator>, <condition> ]
通过列表推导式,Python 开发人员可以根据现有列表简洁地创建新列表,使他们的代码更具可读性和简洁性。通过采用 Haskell 的这个特性,Python 获得了一个强大的工具,可以增强其列表操作能力。
因此,下次您在 Python 中使用列表推导式时,请记住它与有影响力的 Haskell 语言的联系。这证明了思想的异花授粉和编程语言的不断发展。
总结
祝贺!您已经了解了 Python 中的列表推导以及如何使用它来基于现有列表创建新列表。现在,您拥有了利用列表推导式的强大功能编写更高效、更具可读性的代码的工具。
记住,熟能生巧。您在项目中使用列表推导式的次数越多,您就会变得越舒适和熟练。因此,请继续尝试不同的示例,并释放 Python 代码中列表推导式的全部潜力。
我们希望本指南对您有所帮助!如果您有任何问题或想分享您在列表推导式方面的经验,请随时在下面发表评论。祝您编码愉快!
常见问题 (FAQ)
什么是 Python 中的列表推导?
列表推导是 Python 中一种简洁而强大的技术,它允许您通过将现有列表或其他可迭代对象的元素组合在一行代码中来创建列表。
我可以在列表推导式中使用条件吗?
是的,您可以通过在迭代后包含可选条件来在列表推导中使用条件。该条件根据指定的条件筛选元素,允许您有选择地在结果列表中包括或排除项目。
是否有任何内置函数可以与列表推导式一起使用?
是的,Python 提供了各种内置函数,例如 len()
、 max()
、 min()
和 sum()
,可以与列表推导结合使用,对列表执行高级操作。这些函数为您的列表推导式代码增加了多功能性和灵活性。
列表推导式是否比传统循环更快?
通常,列表推导式往往比传统循环更快、更高效,因为它利用了 Python 解释器的优化底层实现。但是,性能差异可能因特定用例而异。
Python 中的 4 种推导式类型是什么?
Python 中的四种推导式类型是列表推导式、字典推导式、集合推导式和生成器推导式。
参考
- Python 文档
- Python 参考en/latest/docs/comprehensions/list_comprehension.html
- 受 Haskell 启发的 Python 列表推导式