天天看点

python symbols函数_[Python] 列表推导式和生成器表达式

python symbols函数_[Python] 列表推导式和生成器表达式

我其实一直不太看得明白写的很简短的代码,在读了几页《流畅的Python》后我明白了——是我太菜。周末花了点时间把自己一直不会用的列表推导式和生成器表达式好好看了下,整理一篇笔记,部分代码是参考了《流畅的Python》中的示例。

在搞明白这俩货之前,如果需要从两个数据源取出数据耦合起来,我大概率会把代码写成这样的德行——

for x in xs:
    for y in ys:
       x_y = (x, y)
           

嗯,要写三行,虽然确实很容易看懂意思,但确实不简洁,再加上经常看到别人写的代码里用到列表推导式和生成器表达式,就更加难受了……

列表推导式,List Comprehension,一般缩写为listcomps

生成器表达式,Generator Expression,一般缩写为genexps

掌握这两个东西,可以让代码更简短,也能让代码运行更省内存。

List Comprehension

给定一个数字,计算这个数字的平方,然后我们要对一堆数字做这个事情,有两个办法——

  • 使用lambda函数+map函数
  • 使用列表推导式

第一个法子,代码会写成这样

print(
    list(map(lambda x: x * x, range(1,11)))
)
           

我是不喜欢lambda的,总觉得这个写法很难看,它等同于下面这样子

def calc(x):
    return x * x
print(
    list(map(calc, range(1,11)))
)
           

而且,还得用上map函数,如果你没用过map函数,比如我就用的少,经常用到的时候想不起来它的两个参数干嘛的……

而如果用列表推导式实现这个呢——

print(
    [x * x for x in range(1,11)]
)
           

一行就可以了,也不需要去写个lambda,或者写个def去给map调了。对Python来说,lambda和列表推导式的使用原则都差不多——不能太长,可读性都会变差,就简短的代码来说,我更喜欢列表推导式,你可以直接把意思读出来——

需要x乘以x,x 从 range(1,11) 中循环取出
           

列表推导式的外部是一个中括号,它提醒你,其永远是返回一个列表对象的

上面的几个写法的执行结果——

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
           

对整个列表推导式分成三部分来看——

  • [ list comprehension ]

    ,外壳为中括号,提醒你返回对象一定是个列表
  • x * x

    ,对循环元素的处理,一个表达式,所以这里也可以是个函数调用
  • for x in range(1,11)

    ,一个for循环,x实际上是一个局部变量,第三部分直接告诉我们x从哪取,这里就从

    range(1,11)

    里取

P.S. Python2的列表推导式是有变量泄漏的问题的,换言之,py2的

x

变量可能会污染外部的同名变量,Python3没这个问题,Python3的列表推导式有自己的作用域了。

上面的代码用普通的for循环写就写成这样了——

x_power = []
for x in range(1,11):
    x_power.append(x * x)
print(x_power)
           

嗯,很丑。我以前都这么写代码的。

再下面这个例子来自《流畅的Python一书》

symbols = '!@#$%^&*'
print([ord(symbol) for symbol in symbols])
           

这里对每个元素的处理就是调用函数

ord

再看最一开始的两层循环,实际上就是笛卡尔积,用列表推导式也很好写

colors = ['Black', 'White', 'Yellow']
sizes = ['XS', 'S', 'M', 'L', 'XL']
t_shirt_models = [(color, size) for color in colors for size in sizes]
print(t_shirt_models)
           

调整for循环的顺序,即可调整实际的循环顺序,它和两层嵌套的循环是等价的

列表推导式里还可以写条件,比如下面这样子

names = ['Addison', 'Lucy', 'Audrey', 'Bella', 'Nova', 'Brooklyn', 'Paisley', 'Savannah',
'Claire', 'Skylar', 'Isla', 'Genesis', 'Naomi', 'Elena', 'Caroline']

names_mt4_from_listcomps = [n for n in names if len(n) > 4]
print(names_mt4_from_listcomps)
           

条件直接嵌入在for循环后面就好了,我要取出的是名字长度大于4个字符的。

如果用lambda函数+filter函数,就得这么写

names_mt4_from_filter = list(filter(lambda n: len(n) > 4, names))
print(names_mt4_from_filter)
           

嗯,括号都得写好几个……

总之,列表推导式看起来就是“顺着读”就好了,所以太长也不合适啦

Generator Expression

生成器表达式写法和列表推导式几乎一样——只要把中括号换成圆括号就好了,所以,衬衫颜色那个例子就变成这么写——

colors = ['Black', 'White', 'Yellow']
sizes = ['XS', 'S', 'M', 'L', 'XL']
t_shirt_models = ((color, size) for color in colors for size in sizes)
print(t_shirt_models)
           

不过,这个打印出来的就不是完整的结果了,而是——

<generator object <genexpr> at 0x0000020CE2EC4C48>
           

一个生成器对象。

这是因为生成器遵循迭代器协议,它是一种有“惰性”的东西,你不对它做操作,它就什么也不做。

所以如果你要处理非常多的数据的时候,数据不需要再复制一次了,数据留在原处就好了,生成器会在“需要”的时候去取数据的,这样就减少了内存的消耗。

理所当然的,你不能在I/O相关的操作里用它,比如去数据库读数据,你要是每次都去读一次,后果不堪设想……

怎么让数据出来呢?简单的办法就是for循环咯

for t in t_shirt_models:
    print(t)
           

当然,用一个函数去处理生成器对象也可以,反正它是可以被“迭代”的。

对比listcomps和genexps

打断点对比可以明显看出两者的差别,先来看列表推导式的——

知乎视频​www.zhihu.com

python symbols函数_[Python] 列表推导式和生成器表达式

列表推导式会把所有的数据都生成出来,存到内存里,然后才轮到

print()

函数从结果里去取数据打印。

而生成器表达式,则是每次

print

运行的时候去生成一次数据。

知乎视频​www.zhihu.com

python symbols函数_[Python] 列表推导式和生成器表达式

最后,推荐一下《流畅的Python》这本书,在你有了一定的Python基础之后,一定要看看这本书。我踩了很多书坑,但这本书不坑。

继续阅读