别再用 for 循环了,有更 Pythonic 的方法!

533 阅读6分钟

为什么要避免使用 for 循环?

你可能会问,为什么要挑战自己在代码中避免编写 for 循环?在 Python 中避免使用 for 循环,这种做法并不是一味地认为 for 循环不好或低效。相反,这是一种拓展自己对 Python 理解的练习,通过探索替代的结构和特性,使代码更加简洁、易读并符合“Pythonic”风格。

通常,for 循环被用于以下场景:

  • 从序列中提取特定信息。
  • 从现有序列创建新序列。
  • for 循环的使用已经成为一种习惯。

幸运的是,Python 提供了丰富的工具来完成这些任务,只需要我们转换一下思维,用新的视角去解决问题。

通过避免使用 for 循环,你可以获得以下好处:

  • 减少代码量:通过利用 Python 的内置函数或列表推导式,通常可以用更少的代码行完成同样的任务,因为这些构造是为简洁地执行常见操作而设计的。
  • 提高代码可读性:使用列表推导式或内置函数的代码通常比使用 for 循环的等效代码更容易阅读和理解。这是因为这些构造将循环机制抽象化,集中在操作本身上。
  • 减少缩进(在 Python 中尤为重要):Python 强烈依赖缩进来定义代码块的结构。通过避免 for 循环,可以减少额外的缩进层级,使代码更清晰、易于理解。这对 Python 特别重要,因为该语言强调代码的可读性和简洁性。

让我们来看一个例子:

with open('example_file.txt', 'r') as file:
    for line in file:
        if 'keyword' in line:
            try:
                value = int(line.strip())
                print(value)
            except ValueError:
                print("Conversion error occurred.")
        else:
            print("Keyword not found in line.")

在这个例子中,代码包含多个嵌套结构,难以阅读。这个例子展示了深层嵌套代码的用法。

在这个代码段中,它混杂了控制流结构(如 withtry-except 块)与业务逻辑(如 for 循环和 if 语句),通过缩进实现结构分隔。通过优先使用缩进来仅为控制流构造服务,核心业务逻辑会变得更加清晰和独立。

列表推导式/生成器

Python 中的列表推导式和生成器表达式是处理和操作列表或其他可迭代集合的紧凑方式。

列表推导式

列表推导式提供了一种简洁的方式来创建列表。它由包含一个表达式的括号构成,接着是一个 for 子句,再后面可以接零个或多个 forif 子句。表达式可以是任何内容,这意味着你可以在列表中放入各种对象。生成的新列表是根据紧随其后的 forif 子句评估表达式而得出的。与常规函数和循环相比,它通常更紧凑且更快。

例如,[x**2 for x in range(10)] 会生成一个包含 0 到 9 的平方数的列表。

生成器表达式

生成器表达式与列表推导式类似,但不生成列表并将所有对象存储在内存中,而是逐个生成和处理项目。这样一来,生成器表达式比相应的列表推导式更加节省内存。

例如,(x**2 for x in range(10)) 会创建一个生成器,它逐个生成 0 到 9 的平方数。

示例:

result = []
for item in item_list:
    new_item = do_something_with(item)
    result.append(item)

可以重写为:

result = [do_something_with(item) for item in item_list]

Map/Reduce 函数

在 Python 中,mapreduce 是对序列(如列表)或可迭代对象应用指定操作,并分别将其简化为单个累计值的函数。

map 函数

map 函数将指定函数应用于可迭代对象(如列表)的每个项目,并返回结果的列表。语法为 map(function, iterable, ...)。当你想对集合中的每个项目执行相同操作而无需编写显式循环时,这很有用。

例如,map(lambda x: x * 2, [1, 2, 3, 4]) 将生成 [2, 4, 6, 8]

reduce 函数

reduce 函数位于 functools 模块中,它将给定函数反复应用于序列的元素,将它们简化为单个值。传递给 reduce 的函数必须接受两个参数,并从左到右累积应用于可迭代对象的项目,从而将其简化为一个值。

例如,reduce(lambda x, y: x + y, [1, 2, 3, 4]) 会将列表中的数字相加,结果为 10

map 用于转换,而 reduce 用于累积。两者都是 Python 中函数式编程风格的例子,在这种风格中你将函数应用于序列和其他可迭代对象。

提取函数

前述方法适合处理较简单的逻辑。那么,面对更复杂的逻辑怎么办?作为程序员,我们编写函数来抽象复杂操作。相同的概念适用于这里。如果你写的代码如下:

results = []
for item in item_list:
    results.append(result)

显然,你将太多职责分配给了一个代码块。相反,我建议你考虑如下方法:

def process_item(item):
    return result

results = [process_item(item) for item in item_list]

有时,你可能需要使用嵌套函数,例如:

results = []
for i in range(10):
    for j in range(i):
        results.append((i, j))

可以重写为:

results = [(i, j)
           for i in range(10)
           for j in range(i)]

有时候,你的代码需要维护一些内部状态,例如:

my_list = [10, 4, 13, 2, 1, 9, 0, 7, 5, 8]
results = []
current_max = 0
for i in my_list:
    current_max = max(i, current_max)
    results.append(current_max)

你可以将其重写为:

from itertools import accumulate

my_list = [10, 4, 13, 2, 1, 9, 0, 7, 5, 8]
results = list(accumulate(my_list, max))

看起来怎么样?代码整体上是否更加符合 Python 风格?此外,第二种方法使用了 itertools 模块中的 accumulate 函数,通常更高效和符合 Python 风格,原因包括:

  • 内置函数效率accumulate 是一个 Python 内置函数,专门用于执行累积操作,相比手动实现的 for 循环,该方法在处理此类任务上更加高效。
  • 可读性accumulate 函数清楚地表明了累积值与特定操作(此例中为 max)的意图,使代码一目了然。
  • 简洁性:第二种方法更简洁,仅用两行代码完成任务,而第一种方法需四行。这减少了出错的可能性并使代码更清晰。
  • 可扩展性与可维护性:使用 accumulate 和其他内置函数使代码更易维护,也更适应变化,例如应用不同的操作而非 max

结论

总之,在 Python 中使用诸如列表推导式和生成器表达式等强大的功能替代 for 循环进行每次迭代操作,可以使代码更具 Python 风格。

这种方法不仅使代码更具可读性和简洁性,还往往带来更好的性能。通过利用这些功能,程序员可以用简洁、易维护的方式表达复杂逻辑,这也符合 Python 简洁优雅的编程哲学。

全文完!

如果你喜欢我的文章,欢迎关注我的微信公众号 deliverit。