小知识:生成器表达式

229 阅读3分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

在多数条件下我们是不需要使用生成器函数,并手动yeild返回值的。

Python为我们提供了一种名为生成器表达式的东西,用于快速的实现生成器。

语法形式如下:

(xxxx for i in range(10)) # 姑且使用10 表示一个可迭代的对象

如果我们把上面的range(10)当作传入的参数,它会相当于这样的生成器函数

def foo(it: Iterable):
  for i in it:
    yield i 

生成器表达式仅仅是这种函数的语法糖而已,但也确实够实用。

因为大多数使用我们都会把操作提取成一些函数,然后分别对对象应用操作。也就是说生成器表示式相当于我们经常对集合做的所谓的map操作。这也是python的惯用法,而不是使用python内置的map函数。而且实际上这个表达式还可以做一个filter操作。具体操作是在表达式的for循环后,添加if语句用于筛选出符合条件的元素。 像下面这样可以使用,可以选出序列中的3的倍数

(i for i in range(100) if i%3 == 0)

生成器表达式还有个好用的点在于,单独需要可迭代对象做调用时,使用生成表达式可以省略括号。

这里我们就顺便完成以到leetcode的题目来演示。

第十四题,最长公共前缀。题意如题,求几个字符串的公共的最长前缀。

我们就从最朴素的想法出发,一位一位的比较,比到不一样了就得到了最长的公共前缀。那么很显然,实际上,最长的情况下,我们也仅仅只需要比较到最短的字符串耗尽,而最短的字符串的前面匹配成功的部分就时前缀。

那么我们的代码可以像下面这样写:

from itertools import takewhile
class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        shortest_str = min(strs, key=len)
        def predicate(index_and_char):
            index, char = index_and_char
            return all(str_[index] == char for str_ in strs)
        return "".join(char for index, char in takewhile(predicate, enumerate(shortest_str)))

这里借助了itertools的takewhile函数,因为如果使用if后面的不匹配也会做比较,而且如果匹配失败之后的位置又有匹配成功的字符,会有不必要的麻烦。

在all函数和join函数中我们也利用了先前说的语法糖。

与循环对比我个人还是更倾向这种生成表达式的做法,思路更清晰。 最后总结一些,一般来讲,在对元素的操作可以比较好的提取成函数操作时,推荐使用生成器表达式。

而当不太好抽取时使用生成器函数。比如上次说的斐波拉契数列,更适合使用函数。当然也不是不能使用表达式,只是需要一些奇怪的操作,比如我们借助海象运算符,可以像下面这样实现:

f0 = 1
f1 = 1
( (f0:=f1-f0,f1:=f1+f0) for i in range(10) )

只是会失去生成器表达式本应拥有的简洁性。