Simple is better than complex – 简单胜于复杂
for 循环是大多数编程语言中非常基本的控制流工具。例如,C 语言中一个简单的 for 循环如下所示:
int i;
for (i=0;i<N;i++)
{
//do something
}
对于复杂的场景,我们通常需要编写难看的嵌套循环nested loops或定义大量辅助变量assistant variables(如上述代码中的 i
)。
幸运的是,在 Python中事情就变得方便多了。我们有很多技巧来编写更优雅的循环loops,它们确实让我们的生活变得更轻松。在 Python 中,嵌套循环nested loops不是不可避免的,辅助变量assistant variables也不是必须的,我们甚至可以自己定制 for 循环。
本文将介绍在 Python 中编写循环loops的一些最有用的技巧tricks。希望它能帮助您感受 Python 之美。
使用 for 循环的一个常见场景是从列表list中获取索引indexes和值values。当我开始学习 Python 时,我写的代码如下:
for i in range(len(my_list)):
print(i, my_list[i])
这当然行得通。但还不够 Pythonic。几个月后,我找到了标准的 Pythonic 方法:
for i, v in enumerate(my_list):
print(i, v)
如上所示,内置的built-in enumerate
函数function可以让我们的生活变得更轻松。
嵌套循环Nested loops令人头疼。它们会降低代码的可读性readability并使事情变得复杂complex。例如,跳出嵌套循环breaking out of the nested loops通常不是很容易。我们需要知道最内层循环inner-most loop何时被破坏,第二个最内层循环second inner-most loop何时被破坏,等等。
幸运的是,Python 内置的built-in itertools
模块中有一个很棒的函数function,叫做 product
。我们可以使用它来避免编写大量嵌套循环nested loops。
让我们通过一个简单的例子来感受一下它有多有用:
list_a = [1, 2020, 70]
list_b = [2, 4, 7, 2000]
list_c = [3, 70, 7]
for a in list_a:
for b in list_b:
for c in list_c:
if a + b + c == 2077:
print(a, b, c)
# 70 2000 7
如上图所示,我们需要三个嵌套循环nested loops,才能从三个列表lists中得到总和等于 2077 的三个数字。代码一点也不整齐。
使用 product
函数进行尝试
from itertools import product
list_a = [1, 2020, 70]
list_b = [2, 4, 7, 2000]
list_c = [3, 70, 7]
for a, b, c in product(list_a, list_b, list_c):
if a + b + c == 2077:
print(a, b, c)
# 70 2000 7
如上图所示,在 product
函数的帮助下,只需要一个循环one loop。
因为 product
函数会根据输入的可迭代对象 生成笛卡尔积Cartesian product。它可以帮助我们在很多情况下避免嵌套循环nested loops。
事实上, product
函数function只是冰山一角。如果您探索 Python 内置的built-in itertools
模块module。一个新世界将向你敞开大门。这个工具箱toolbox包含了许多有用的方法methods来满足我们对循环loops的需求。它们的完整列表可以在官方文档official document中找到。让我们在此欣赏一些有趣的用法。
至少有三种方法可以实现无限循环:
count
函数import itertools
natural_num = itertools.count(1)
for n in natural_num:
print(n)
# 1,2,3,...
cycle
函数import itertools
many_yang = itertools.cycle('Zhang')
for y in many_yang:
print(y)
# 'Z','h','a','n','g','Z','h','a',...
repeat
函数import itertools
many_zhang = itertools.repeat('Zhang')
for y in many_zhang:
print(y)
# 'Zhang','Zhang',...
chain()
函数可以帮助我们将多个可迭代对象multiple iterables 合并为一个chain object。
from itertools import chain
list_a = [1, 22]
list_b = [7, 20]
list_c = [3, 70]
for i in chain(list_a, list_b, list_c):
print(i)
# 1,22,7,20,3,70
groupby
函数用于在迭代器iterator中找出相邻的重复项adjacent duplicate items,并将它们放在一起。
from itertools import groupby
for key, group in groupby('ZHhHANNGGG'):
print(key, list(group))
# Z ['Z']
# H ['H']
# h ['h']
# H ['H']
# A ['A']
# N ['N', 'N']
# G ['G', 'G', 'G']
如上图所示,相邻的相同字符被放在了一起。此外,我们还可以告诉 groupby
函数如何判断两个项目是否相同:
from itertools import groupby
for key, group in groupby('ZHhHANNGGG', lambda x: x.upper()):
print(key, list(group))
# Z ['Z']
# H ['H', 'h', 'H']
# A ['A']
# N ['N', 'N']
# G ['G', 'G', 'G']
在欣赏了上述所有示例之后,是时候思考一下为什么 Python 中的 for 循环如此灵活和优雅了。在我看来,这是因为我们可以在 for 循环的迭代器iterator中应用函数。上面提到的所有技巧都是在迭代器iterator中使用一些特殊的函数。所有技巧的模板如下:
for x in function(iterator)
在底层,内置的 itertools
模块只是为我们实现了一些常用函数function。如果我们不小心忘记了其中的某个函数function,或者找不到我们需要的函数function,我们可以自己编写一个。更具体地说,这些函数function就是生成器generators。这就是为什么我们可以通过它们生成无限循环。
简而言之,我们可以通过编写自定义的生成器generator,随心所欲地定制 for 循环。
让我们来看一个简单的例子:
def even_only(num):
for i in num:
if i % 2 == 0:
yield i
my_list = [1, 9, 3, 4, 2, 5]
for n in even_only(my_list):
print(n)
# 4
# 2
如上例所示,我们定义了一个名为 even_only
的生成器generator。如果在 for 循环中使用该生成器generator,列表中将只遍历偶数。
当然,上面的例子只是用于解释。还有其他方法可以做同样的事情,比如使用列表推导式List Comprehension。
my_list = [1, 9, 3, 4, 2, 5]
for n in (i for i in my_list if not i % 2):
print(n)
# 4
# 2
用 Python 编写循环可以非常灵活和优雅。我们可以适当使用一些内置工具,甚至可以自己定义生成器generators来编写简洁的循环。