



使用列表推导式创建如下列表:
当需要列表时,可以直接使用推导式。但有时不是真的需要列表,而是想使用不会大幅增加内存占用的方法。例如第1章开头的示例:
必须等待列表many_squares全部创建完毕,即分配好所有内存,才能调用函数do_something_with(),这会导致内存使用量急剧增加。
解决方法之一是编写并调用生成器函数。更简单的方法是编写生成器表达式。虽然正式名称是生成器表达式,但笔者认为称为“生成器推导式”更合适。生成器表达式的语法与列表推导式相似,区别在于使用圆括号而不是方括号:
生成器表达式创建了一个生成器对象,就像列表推导式创建列表对象一样。只需将"["和"]"替换为"("和")",就可以像列表推导式一样创建生成器对象。
这样,就能直接创建生成器对象,无须定义生成器函数。换言之,生成器表达式是创建生成器对象的快捷方式:
对于Python语言,这两个版本的many_squares完全等价。
列表推导式的特性同样适用于生成器表达式,即多个for语句、多个if语句等,用户只需使用圆括号。
在实践中,有时可以省略圆括号。当将生成器表达式作为参数传递给函数时,可能会出现双圆括号的情况,即“((代码))”。在这种情况下,可以省略内层的圆括号。
假设需要对客户邮箱地址列表进行排序,针对状态为active的客户:
注意,生成器表达式大幅提高了代码可读性(只要稍微练习,代码就很自然)。还需要注意一点,只有在将生成器表达式传递给单参数的函数或方法时,才能嵌入生成器表达式。否则,会引发语法错误:
由于语法模糊,Python无法理解代码的含义,因此,必须使用内层的括号:
有时,将生成器表达式赋值给变量,可读性更高:
无括号的生成器表达式能统一表示生成器表达式和列表推导式。下面是生成平方数序列的生成器表达式:
再次传入内置的list()函数:
下面是一个列表推导式:
理解生成器表达式后,很容易发现列表推导式其实是生成器表达式的衍生数据结构。字典推导式和集合推导式也是生成器表达式的衍生结构。尽管Python内部并非如此,但这种思维方式完全符合Python的语义。
理解了这一点,读者不妨在代码中多使用生成器表达式,以提高代码的可读性、可维护性和性能。
既然生成器表达式优势显著,为什么人们还会选择列表推导式呢?一般而言,使用生成器表达式可使代码更加灵活和高效,除非必须使用列表。以下几点需要注意:
首先,如果序列不长,对内存占用的影响较小,则使用生成器表达式的收益也小。这里所说的“长”,是指至少包含数千个元素。
其次,生成器表达式并非总是符合编程设计模式。如果需要随机访问,或者迭代序列两次,生成器表达式就不适用了。如果需要添加或删除元素,或者改变某个索引处的值以便稍后查找,生成器表达式同样无效。
当编写返回值为序列的方法或函数时,开发者需要确定返回的对象是生成器表达式,还是列表推导式。
在理论上,与其返回列表,不如返回生成器对象。调用者通过调用list(),即可将生成器对象转换为列表。在实践中,也许调用者就是想获取列表。强制返回生成器对象,则导致画蛇添足。另外,如果函数内部已经构造好了列表,直接返回列表更加合适,没有必要使用生成器表达式。
如果你的目的是创建普通程序员也能使用的库,则返回列表更加合理。几乎所有程序员都熟悉列表,但很少有人熟悉Python生成器的工作原理,在面对生成器时可能感到困惑。