Erlo

详解python中的生成器表达式

2019-10-24 11:30:20 发布   261 浏览  
页面报错/反馈
收藏 点赞

什么是生成器表达式

还记得列表解析吗?我们把[]换成()就变成生成器表达式了。

g = (x for x in [1, 2, 3, 4])
print(g)  # <generator object <genexpr> at 0x000001CDC3A6AA50>

# 我们可以调用__next__()进行迭代
print(g.__next__())  # 1

# 还可以使用list一次性全部迭代
# 没有1是因为上面已经把第一个元素yield出来了
print(list(g))  # [2, 3, 4]

生成器表达式的解析

我们知道生成器表达式只有在迭代的时候才会产出值,或者说才会执行里面的代码

arr = [1, 1, 2, 3, 3, 4, 4]
g = (x for x in [1, 2, 3, 4] if arr.count(x) > 1)
print(list(g))  # [1, 3, 4]

这个结果是毋庸置疑的,只有1、3、4在arr中出现的次数超过了一次。但是我想问的是,如果我不定义这个arr,那么你觉得会报错吗?

g = (x for x in [1, 2, 3, 4] if arr.count(x) > 1)

首先这么写是不报错的,为什么?很明显arr没有定义啊,这就是我们之前说的生成器表达式只有在迭代的时候才会执行里面的代码。这意味着什么,说明了它根本就不会管if后面是什么东西。但是当我们生成值的时候,就肯定不行了,因为这样就会执行里面的代码,检测到arr没有被定义,于是抛出一个NameError

g = (x for x in [1, 2, 3, 4] if 巭孬嫑烎 > 1)
print("程序会执行到这一步的")  # 程序会执行到这一步的
try:
    g.__next__()
except NameError as e:
    print(e)  # name '巭孬嫑烎' is not defined

# 同理如果改成 g = (xxx for x in [1, 2, 3, 4])也不会报错
# 因为for前面的内容也是在生成值的时候才会去真正执行
# 当迭代的时候,才会报出name 'xxx' is not defined

但是,我要说但是了。对于in后面的内容,这是在编译的时候就已经确定了。python也是编译成字节码的,在编译的时候就会检测in后面的变量有没有被定义。如果没有定义,解释执行的时候直接抛出异常。

try:
    g = (x for x in arr)
except NameError as e:
    print(e)  # name 'arr' is not defined

抛出一个小问题,如果上面仔细看了,肯定是可以理解的

arr = [1, 2, 3]
g = (x for x in arr if x in arr)
print(list(g))  # [1, 2, 3]
"""
这是显然的,依次遍历arr,看元素是否出现在arr中。
遍历的就是arr,所以当然在arr里面,因此结果就是arr全部
"""

################ 分割线 ##################
arr = [1, 2, 3]
g = (x for x in arr if x in arr)
arr = [1, 22, 3]
print(list(g))  # [1, 3]
"""
这里我把arr给改了,就出现了这个结果,原理解释过了。
因为生成器表达式中的if后面的语句只有在迭代的时候才会真正执行,才会去找arr,虽然也有in,但它是在if语句里面的。
而在迭代之前我把arr给该了,因此迭代的时候使用的修改之后的arr

而对于生成器表达式里面的in来说,会在编译的时候就确定in后面变量,如果没有,执行就抛异常
因此如果有的话,那么在生成器表达式即便还没迭代的时候,就已经有一个指针指向了arr(所指向的值)
因为在in后面,[1, 2, 3]这份内存相当于除了arr,还有一个指针指向它(因为放在in后面)。
因此此时就和arr没有关系了,即使arr改成了[1, 22, 3],in后面的还是开始的[1, 2, 3]
"""

生成器表达式内部迭代变量的作用域

其实无论是生成器表达式、还是列表解析等等,内部用于迭代的变量都是临时变量,不会影响到其他变量的

i = 0
[i for i in range(10)]
print(i)  # 0

# 这里的i不是临时变量
for i in range(10):
    pass
print(i)  # 9

# 再比如说
x = 1
# y是用于迭代的变量,是临时变量,会受到保护的,不会影响其它值,也不会被其它值影响
# 但是x显然不是临时变量,它是会被外界影响的
g = (x + y for y in [1, 2, 3])
x = 5
y = 3  # 没啥用
print(list(g))  # [6, 7, 8]
"""
5+1, 5+2, 5+3
"""

这里面有一个很经典的问题,看看你是否会绕进去。

g = (i for i in range(4))

for i in [1, 10]:
    g = (i + j for j in g)

print(list(g))

你是这样分析的吗?第一次循环,1+0, 1+1, 1+2, 1+3,然后第二次迭代。10+1+0, 10+1+1, 10+1+2, 10+1+3,所以最会list(g)的结果是11,12,13,14,你是这样算的吗?如果是的话说明你很聪明的被坑了。我们来分析一下。

g = (i for i in range(4))

for i in [1, 10]:
    g = (i + j for j in g)

print(list(g))  # [20, 21, 22, 23]
"""
可以看到结果如上,为什么是这个结果呢?
首先我们之前说过,in后面的变量在编译的时候就已经确定了,然后执行的时候去找。
找不到就报错,即使你没有迭代生成器。如果有,那么in就已经确定了,即使后续修改也不影响,因为已经指向了最开始的值。
但是对于for之前的变量,只有在产出值的时候才会执行

那么当第一次迭代的时候,相当于g = <gen i+0,i+1,i+2,i+3>,这里i是不确定的,是会被影响的,所以是i。至于i是多少,只能迭代完之后才知道
但j是临时变量,受保护的。无论什么时候,j都不会被影响,永远是0,1,2,3
第二次迭代,g = <gen i+i+0, i+i+1, i+i+2, i+i+3>,迭代完毕之后i=10,所以是20, 21, 22, 23
"""

おしまい

分享图片

大好きなみなみ

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认