向正在运行的生成器添加局部变量

1 投票
3 回答
2569 浏览
提问于 2025-04-15 12:57

最近,我尝试从一个正在运行的生成器外部设置局部变量。生成器的代码也需要访问这些变量。

一个问题是,当我试图访问这些变量时,解释器似乎认为它们是全局变量,因为在局部范围内没有设置这些变量。但我并不想改变全局变量,也不想把整个全局范围复制过来,让这些变量看起来像是局部的。

另一个问题是,似乎从外部访问局部(和全局?)字典时,它们是只读的。

有没有什么合法(或者至少部分合法)的方法可以在一个正在运行的生成器实例中引入局部变量?

补充说明:

我不是指“send”函数。这个函数当然很不错,但因为我想设置多个不同名称的变量,所以对我来说并不方便。

3 个回答

1

locals()这个函数总是返回一个只读的字典,也就是说你不能直接修改它。不过,你可以自己创建一个“locals”字典:

def gen_func():
    lcls = {}
    for i in range(5):
        yield (i, lcls)
        print lcls


for (val, lcls) in gen_func():
    lcls[val] = val

其他任何可以改变内容的结构也可以使用。

1

如果你想要一个可以作为接收器的协程或生成器,你应该使用send方法,就像Stephan202的回答中提到的那样。如果你想通过设置生成器中的各种属性来改变运行时的行为,有一个老的示例是Raymond Hettinger写的:

def foo_iter(self):
    self.v = "foo"
    while True:
        yield self.v

enableAttributes(foo_iter)
it = foo_iter()
print it.next()
it.v = "boo"
print it.next()

这段代码会打印:

foo
boo

enableAttributes这个函数转换成一个合适的装饰器应该不会太难。

5

你可能在寻找的是 send 方法,它可以让你把一个值 发送到 生成器里。这个参考资料提供了一个例子:

>>> def echo(value=None):
...     print "Execution starts when 'next()' is called for the first time."
...     try:
...         while True:
...             try:
...                 value = (yield value)
...             except Exception, e:
...                 value = e
...     finally:
...         print "Don't forget to clean up when 'close()' is called."
...
>>> generator = echo(1)
>>> print generator.next()
Execution starts when 'next()' is called for the first time.
1
>>> print generator.next()
None
>>> print generator.send(2)
2
>>> generator.throw(TypeError, "spam")
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.

让我给你一个我自己的例子。(注意!上面的代码是 Python 2.6 的,但下面我会写 Python 3;py3k 参考

>>> def amplify(iter, amp=1):
...     for i in iter:
...         reply = (yield i * amp)
...         amp = reply if reply != None else amp 
... 
>>> it = amplify(range(10))
>>> next(it)
0
>>> next(it)
1
>>> it.send(3) # 2 * 3 = 6
6
>>> it.send(8) # 3 * 8 = 24
24
>>> next(it) # 4 * 8 = 32
32

当然,如果你真的想的话,也可以不使用 send。比如,把生成器放在一个类里面(但这样就没那么优雅了!):

>>> class MyIter:
...     def __init__(self, iter, amp=1):
...         self.iter = iter
...         self.amp = amp
...     def __iter__(self):
...         for i in self.iter:
...             yield i * self.amp
...     def __call__(self):
...         return iter(self)
... 
>>> iterable = MyIter(range(10))
>>> iterator = iterable()
>>> next(iterator)
0
>>> next(iterator)
1
>>> iterable.amp = 3
>>> next(iterator)
6
>>> iterable.amp = 8
>>> next(iterator)
24
>>> next(iterator)
32

更新:好的,现在你更新了你的问题,让我再试一次。也许这就是你想要的意思?

>>> def amplify(iter, loc={}):
...     for i in iter:
...         yield i * loc.get('amp', 1)
... 
>>> it = amplify(range(10), locals())
>>> next(it)
0
>>> next(it)
1
>>> amp = 3
>>> next(it)
6
>>> amp = 8
>>> next(it)
24
>>> next(it)
32

注意,locals() 应该被视为只读,并且是依赖于作用域的。正如你所看到的,你需要明确地把 locals() 传递给生成器。我看不出有什么其他的方法可以做到这一点……

撰写回答