1

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

一个问题是,当访问变量时,解释器认为它必须是一个全局变量,因为变量没有设置在本地范围内。但我不想更改全局变量,也不想复制整个全局范围以人为地使变量局部化。

另一个麻烦是,当从外部访问时,本地人(和全局人?)的字典接缝为只读。

是否有任何合法(或至少部分合法的方式)将本地人引入正在运行的生成器实例?

编辑澄清:

我不是指“发送”功能。这当然是一个简洁的功能,但是由于我想设置多个具有不同名称的变量,因此对我的目的来说并不方便。

4

3 回答 3

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 ref

>>> 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()给生成器。我看不出有什么办法...

于 2009-07-16T22:56:04.813 回答
1

locals() 总是返回一个只读字典。您可以创建自己的“本地人”字典:

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


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

任何其他可变结构也可以工作。

于 2009-07-16T22:38:18.330 回答
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将函数转换为适当的装饰器 应该不会太难。

于 2009-07-16T23:59:36.340 回答