如何检查Generator类型对象?

时间:2017-06-16 05:26:43

标签: python python-2.7 generator coroutine yield-keyword

使用以下代码(第一种情况),

def f():
   mylist = range(3)
   for i in mylist:
      yield i*i

在不检查y的情况下,您能说,y=f()返回(x*x for x in range(3))类型的collections.abc.Generator对象吗?

使用以下代码(第二种情况),

def func():
    x = 1
    while 1:
        y = yield x
        x += y

调用Generator时返回的y=func()类型对象是什么?您如何检查y以查看代码?

3 个答案:

答案 0 :(得分:2)

第一种情况 - 简单生成器

generator expression (x*x for x in range(3))与您描述的simple generator功能大致相同。但是,genexp的范围可能会稍微复杂一些(这就是我们通常建议您立即使用生成器表达式而不是传递它们的原因)。

第二种情况 - 增强型发电机

y = yield x的代码是enhanced generator的示例,用于将数据发送到正在运行的生成器,实质上是在运行的生成器和调用代码之间创建双向通信通道。

发送/接收逻辑的主要用例是实现协同程序和生成器trampolines。请参阅David Beazley的trampoline example

增强型生成器是Twisted Python漂亮的Inline Callbacks实现coroutines的关键。

如何检查发电机

对于y = func()中的变量 y ,唯一的检查技术是检查公共API:

>>> y = func()
>>> dir(y)
['__class__', '__delattr__', '__doc__', '__format__',
 '__getattribute__', '__hash__', '__init__', '__iter__',
 '__name__', '__new__', '__reduce__', '__reduce_ex__',
 '__repr__', '__setattr__', '__sizeof__', '__str__',
 '__subclasshook__', 'close', 'gi_code', 'gi_frame',
 'gi_running', 'next', 'send', 'throw']

如何检查发电机功能

对于生成器函数本身,您可以使用dis模块检查代码以查看其工作原理:

>>> def func():
        x = 1
        while 1:
            y = yield x
            x += y

>>> import dis
>>> dis.dis(func)
  3           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  4           6 SETUP_LOOP              21 (to 30)

  5     >>    9 LOAD_FAST                0 (x)
             12 YIELD_VALUE         
             13 STORE_FAST               1 (y)

  6          16 LOAD_FAST                0 (x)
             19 LOAD_FAST                1 (y)
             22 INPLACE_ADD         
             23 STORE_FAST               0 (x)
             26 JUMP_ABSOLUTE            9
             29 POP_BLOCK           
        >>   30 LOAD_CONST               0 (None)
             33 RETURN_VALUE 

使用调试器跟踪代码

您可以使用pdb调试程序逐步跟踪代码。

>>> import pdb
>>> y = func()
>>> pdb.runcall(next, y)
> /Users/raymond/Documents/tmp.py(2)func()
-> x = 1
(Pdb) s
> /Users/raymond/Documents/tmp.py(3)func()
-> while 1:
(Pdb) s
> /Users/raymond/Documents/tmp.py(4)func()
-> y = yield x
(Pdb) p locals()
{'x': 1}
(Pdb) s
> /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/bdb.py(440)runcall()
-> self.quitting = 1
(Pdb) s
1
>>> pdb.runcall(y.send, 10)
> /Users/raymond/Documents/tmp.py(5)func()->1
-> x += y
(Pdb) s
> /Users/raymond/Documents/tmp.py(4)func()->1
-> y = yield x
(Pdb) locals()
{'__return__': 1, 'x': 11, 'y': 10}

答案 1 :(得分:0)

生成器的目标是按需生成值。换句话说,如果你需要整数的平方,但是你事先并不知道它们中有多少你可以使用generators来生成值。它是使用yield代替return来实现的。在yield函数确实保留上下文之后。您可以将yield想象为"暂停"并返回"停止"。即使您知道集合的大小,生成器的另一个好处是,您不会立即将所有内容加载到内存中,但您一次只能使用一个元素。

答案 2 :(得分:0)

在您的第二个示例中,您将收到以下错误:

for i in func():
    print(i)

因为y = yield x表达式需要send()方法调用: 见https://www.python.org/dev/peps/pep-0342/#specification-sending-values-into-generators

“for”语句调用返回生成器对象的iter(foo())函数。 然后“for”语句将调用next(generator),而生成器不会引发StopIteration异常。

对于你的第二个例子:

for i in func():
    print(i)

“for”语句将获取生成器对象,然后为他调用next(generator object)。对于此调用,它将获得x值(1)。此时,生成器对象等待send(something)方法调用将some值设置为y的内容。如果此方法无法调用,则next(generator object)调用会将None发送给生成器。在这种情况下,y将采用None值,程序将在x += y

上引发错误

正确使用你的第二个例子是:

f = func()
# open generator object
next(f)
#1
f.send(2)
#3