Ⅰ起因

  学习python的同学通常会遇到这样一道经典生成器测试题:

def gen():
for i in range(4):
yield i
base = gen()
for n in (2,10):
base = (i+n for i in base)
print(list(base))
[21,22,23,24]

#简单解答:
因为for循环了两次,并对base从新赋值了,所以可以简化为(i+n for i in (i+n for i in base)) 而n 全部引用了后赋值的10。最里面的base引用的是gen。

答案及解释

   但是这个解答并没有回答一个核心问题:为什么最里层的n 始终用的是10,而base可以找到之前的gen()?

     为了简化问题,我把这道题改造了成这样:

a1 = 3
b = (i for i in range(a1))
a1 = 5
list(b) #[0, 1, 2] a1 = 3
b = (a1 for i in range(3))
a1= 5
list(b) #[5, 5, 5]

  或许各位会猜测:这个问题可能和for后面的数据类型有关系吧?

Ⅱ原理探索

  但如果把range()和前面的数值都改造为列表,结果如下:

a1 = 1
b=([a1,] for i in range(3))
a1=2
list(b) # [[2], [2], [2]] a1 =1
b = (i for i in [a1,])
a1 = 2
list(b) # [1] #也可以把以上两个表达式结合一下
a1 = 1
b = ([a1,i] for i in [a1,])
a1 = 2
list(b) #[[2, 1]]

显而易见,当变量在for前面的时候,会引用后声明的值,而当变量在for后面的iterator中的时候会引用之前声明的值,并且与数据类型无关。

By the way, 可能很人多还不确定列表本身的设定:a =1     b = [a,]       a =2     print(b)   #[1,]

当然以上全部是生成器表达式。如果手动定义一下生成器呢?

a =1
def zz():
for i in [a,]:
yield [a,i]
cc = zz()
a=2
print(list(cc)) #[[2, 2]] #如果传入a
a =1
def zz(a):
for i in [a,]:
yield [a,i]
cc = zz(a)
a=2
print(list(cc)) #[[1,1]]

  生成器函数的测试结果是前后一致,不存在这个问题。

  进一步测试:

a = 1
c = ([b,i] for i in [a,])
b = 1
list(c) #[[1, 1]] # 但是如果a在生成器表达式后面定义的话:
c = ([b,i] for i in [a,])
b = 1
a = 1
list(c) # 会报错 #p.s. 在生成器函数也不会报错

Ⅲ执行效率比较

  对于简单的生成器,生成器表达式更方便、更直观。那么两者的执行效率是否存在差异呢?Timeit!

import timeit
def b():
a = 9999
def c():
for i in range(a):
yield i
list(c())
print(timeit.timeit(stmt=b,number=1000))

函数模式

import timeit
def b():
a = 9999
c = (i for i in range(a))
list(c)
print(timeit.timeit(stmt=b,number=1000))

表达式

结果:

    函数模式    表达式模式

    1.260876    1.235369  
    1.253225    1.238639
    1.256804    1.235393
    1.258575    1.238165

我们看到生成器表达式提供的便利的确是以效率的损耗作为代价的。

进一步的验证表明:生成器表达式初始化的过程相比生成器函数需要花费更多的时间(接近2倍),但是由于初始化的时间过短,并不是形成差距的主要原因。函数模式的生成器会随着next()次数的增加在时间上逐步拉开与生成器表达式差距。调用效率的差距才是主要原因。

Ⅳ结论

  生成器表达式,会在程序执行的过程中运行for 后面的代码,并对for后面的代码进行赋值,而for之前的代码以及生成器函数并不会执行,只会进行编译。

  尽管,生成器表达式代码更简洁,但在生成器初始化和生成器调用的效率上都表现出了与传统生成器函数的差距。

注:列表推导式并不存在这样的问题(当然也不应该出现)

最新文章

  1. WPF 子窗体关闭,刷新父窗体
  2. visio二次开发初始化问题
  3. 用Charles抓取https接口数据
  4. django模板里循环变量<table>里想要两个一行如何控制
  5. bzoj 1208: [HNOI2004]宠物收养所 set
  6. const char*、char*、char* const、char[]、string的区别
  7. 转--Server “**” has shut down the connection prematurely一例分析
  8. 【Java基础】static关键字的理解
  9. (转载)PHP数组传递是值传递而非引用传递
  10. shell脚本实现检測回文字符串
  11. #event.initMouseEvent
  12. Objective-c 数据类型
  13. sql server中的系统数据库
  14. ACM录 之 常识和错误。
  15. ionic2 基于ngx-translate实现多语言切换,翻译
  16. git format-patch制作内核补丁
  17. HDU 5361 In Touch (2015 多校6 1009 最短路 + 区间更新)
  18. 汇编语言---键盘KeyCode值列表
  19. 出现java.lang.Exception: java.lang.RuntimeException: java.lang.NoSuchMethodException: com.web.visit.main.ClickVist$VisitMapper.<init>()的问题
  20. 在Delphi中如何获得SQL中存储过程的返回值?

热门文章

  1. IO分类
  2. 学生管理系统(javaweb版)
  3. C语言博客作业01--分支、顺序结构
  4. 通过ajax返回值
  5. gridview 选中某行后 某行的按钮显示,无选中则隐藏
  6. 【CSS】面试知识整理
  7. CentOS7.2 1511部署RabbitMQ
  8. Mac 怎么通过自带终端连接linux服务器
  9. unity 动态更新模型透明度
  10. canvas中插入的图片 自适应 ?