【问题标题】:Accessing Local Variables Inside a Python Generator在 Python 生成器中访问局部变量
【发布时间】:2011-01-19 14:38:43
【问题描述】:

如何从生成器外部访问定义在 Python 生成器内部的局部变量?

我有一个案例,我的生成器操作本地状态,对于单元测试,我想检查这个状态以确保它包含正确的值。

我无法将状态存储到实例变量(例如 self.state = blah),因为我可能会从同一个类实例创建多个生成器,这意味着生成器可能会覆盖彼此的状态。我也无法在 yield 表达式中返回状态,因为状态名称可能会因各个生成器实例而改变或变化。

例如我想做这样的事情(尽管这段代码不起作用)

from random import random

class MyIter(object):
    def __iter__(self):
        context = {}
        for i in xrange(10):
            context[random()] = random()
            yield i

obj = MyIter()
i1 = iter(obj)
i2 = iter(obj)
while 1:
    try:
        i1.next()
        i2.next()
        print i1.context
        print i2.context
    except StopIteration:
        break

是否可以通过检查 Python 的执行堆栈来访问局部变量?

【问题讨论】:

    标签: python scripting iterator generator


    【解决方案1】:

    很抱歉回答我自己的问题,但是在深入了解生成器界面后,我找到了访问生成器局部变量所需的确切路径:

    from random import random
    
    class MyIter(object):
        def __iter__(self):
            context = {}
            for i in xrange(10):
                context[random()] = random()
                yield i
    
    obj = MyIter()
    i1 = iter(obj)
    i2 = iter(obj)
    while 1:
        try:
            i1.next()
            i2.next()
            print i1.gi_frame.f_locals['context']
            print i2.gi_frame.f_locals['context']
        except StopIteration:
            break
    

    【讨论】:

    • 哎哟!不要这样做,这是 CPython 的一个实现细节。它可能会随着更新的版本而改变,并且对于 Python 的不同实现(如 Jython 或 IronPython)会有所不同。而且它的可读性也不是很好......
    • 我不太可能使用过 Jython 或 IronPython,它们都与“标准”CPython 功能有很多不兼容之处,但这一点很好。
    • +1 虽然我永远不会依赖任何在正常操作期间必须访问生成器内部状态的代码,但能够做到这一点非常有用调试期间的数据,因为有几次 Python 让我想知道,“你到底在想什么?” (另外,我不会因为回答自己的问题而感到难过,除非答案是错误的。;))
    【解决方案2】:

    您应该将生成器视为黑匣子。单元测试不应该关心它的内部状态,因为那只是一个实现细节;他们应该只关心指定的行为。

    【讨论】:

      【解决方案3】:

      如果你真的想这样做,请将迭代器类与容器类分开:

      from random import random
      
      class MyContainer(object):
          def __iter__(self):
              return MyIter(self)
      
      class MyIter(object):
          def __init__(self, container):
              self.container = container
              self.context = {}
              self.it = iter(xrange(10))
          def next(self):
              self.context[random()] = random()
              return next(self.it)
          def __iter__(self):
              return self
      
      obj = MyContainer()
      # ...
      

      虽然我不认为这很有用......

      【讨论】:

      • 谢谢。这是一种非常干净且面向对象的方法,尽管它增加了一些额外的复杂性。
      猜你喜欢
      • 1970-01-01
      • 2012-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-04
      • 2020-06-29
      • 2017-10-17
      相关资源
      最近更新 更多