【问题标题】:How to inspect Generator type object?如何检查生成器类型对象?
【发布时间】:2017-11-18 18:28:06
【问题描述】:

下面的代码(第一种情况),

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

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


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

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

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

【问题讨论】:

  • 详细解释你的问题,stackoverflow.com/a/231855/8150371
  • @Stack 我已经通过这个答案了
  • 如果你只想要一个列表作为返回,调用这样的函数,y = list(f())
  • 我不太明白你在问什么。 type(f) is type(func) 的结果是否回答了您的问题?
  • 对不起,也许我把事情弄糊涂了。我的意思是type(f()) is type(func())ffunc 都返回 相同的东西,一个类型为 generator 的对象

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


【解决方案1】:

第一种情况——简单生成器

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

第二种情况——增强的生成器

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

发送/接收逻辑的主要用例是实现协程和生成器蹦床。请参阅 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}

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      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) 而 generator 不会引发 StopIteration 异常。

      第二个例子:

      for i in func():
          print(i)
      

      “for”语句将获取生成器对象,然后为他调用next(generator object)。对于此调用,它将获得 x 值(1)。此时,生成器对象等待send(something) 方法调用什么会将某物值设置为 y。如果此方法不调用,next(generator object) 调用将发送None 到生成器。在这种情况下,y 将采用 None 值,并且程序将在 x += y 上引发错误

      第二个例子的正确用法是:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-24
        • 1970-01-01
        • 1970-01-01
        • 2020-03-23
        • 1970-01-01
        • 2012-07-03
        • 2011-01-27
        相关资源
        最近更新 更多