我想尝试使用python with
块来将修饰符应用于该块中的操作。但我不确定在协同程序存在的情况下是否可以做到这一点。
例如,假设我有一个WithContext
对象暂时按下这样的堆栈:
class WithContext:
stack = []
def __init__(self, val):
self.val = val
def __enter__(self):
WithContext.stack.append(self.val)
def __exit__(self, exc_type, exc_val, exc_tb):
WithContext.stack.pop()
def do_scoped_contextual_thing():
print(WithContext.stack[-1])
(显然堆栈成员必须是线程本地的,但暂时忽略它。)
然后这段代码:
with WithContext("a"):
do_scoped_contextual_thing()
with WithContext("b"):
do_scoped_contextual_thing()
with WithContext("c"):
do_scoped_contextual_thing()
将打印:
a
b
c
但现在假设我有一个协程情况:
def coroutine():
with WithContext("inside"):
yield 1
do_scoped_contextual_thing()
yield 2
with WithContext("outside"):
for e in coroutine():
do_scoped_contextual_thing()
print("got " + str(e))
我希望此代码输出:
outside
got 1
inside
outside
got 2
但实际上它会输出:
inside
got 1
inside
inside
got 2
外部变为内部,因为协程内的__enter__
将值放在堆栈顶部,并且在协程结束之前不调用__exit__
(而不是不断地进入和退出 - 当你跳进和离开协程时。)
有没有办法解决这个问题?是否存在“coroutine-local”变量?
答案 0 :(得分:1)
我对此感觉不太好,但我确实修改了你的测试代码,重新输入了几次协同程序。与@ CraigGidney的解决方案类似,它使用inspect
模块访问和缓存有关创建WithContext
对象的调用堆栈(也就是“范围”)的信息。
然后我基本上搜索堆栈以查找缓存值,并使用id
函数尝试避免保持对实际帧对象的引用。
import inspect
class WithContext:
stack = []
frame_to_stack = {}
def __init__(self, val):
self.val = val
def __enter__(self):
stk = inspect.stack(context=3)
caller_id = id(stk[1].frame)
WithContext.frame_to_stack[caller_id] = len(WithContext.stack)
WithContext.stack.append( (caller_id, self.val))
def __exit__(self, exc_type, exc_val, exc_tb):
wc = WithContext.stack.pop()
del WithContext.frame_to_stack[wc[0]]
def do_scoped_contextual_thing():
stack = inspect.stack(context=0)
f2s = WithContext.frame_to_stack
for f in stack:
wcx = f2s.get(id(f.frame))
if wcx is not None:
break
else:
raise ValueError("No context object in scope.")
print(WithContext.stack[wcx][1])
def coroutine():
with WithContext("inside"):
for n in range(3):
yield 1
do_scoped_contextual_thing()
yield 2
with WithContext("outside"):
for e in coroutine():
do_scoped_contextual_thing()
print("got " + str(e))
答案 1 :(得分:0)
一个可能的半破解“解决方案”是将上下文与堆栈框架的位置相关联,并在查找上下文时检查该位置。
class WithContext:
_stacks = defaultdict(list)
def __init__(self, val):
self.val = val
def __enter__(self):
_, file, _, method, _, _ = inspect.stack()[1]
WithContext._stacks[(file, method)].append(self.val)
def __exit__(self, exc_type, exc_val, exc_tb):
_, file, _, method, _, _ = inspect.stack()[1]
WithContext._stacks[(file, method)].pop()
@staticmethod
def get_context():
for frame in inspect.stack()[1:]:
_, file, _, method, _, _ = frame
r = WithContext._stacks[(file, method)]
if r:
return r[-1]
raise ValueError("no context")
请注意,不断查找堆栈帧比仅传递值更昂贵,并且您可能不想告诉别人您写这个。
请注意,在更复杂的情况下,这仍然会中断。
例如:
答案 2 :(得分:0)
我遇到了同样的问题。基本上我希望能够在进入/离开协程的运行上下文时执行代码,在我的情况下,即使在交错yield
s的情况下,也要保持一个正确的调用堆栈。事实证明,tornado以StackContext的形式支持此问题,可以按如下方式使用:
@gen.coroutine
def correct():
yield run_with_stack_context(StackContext(ctx), other_coroutine)
其中ctx
是在事件循环执行enter
时将exit
和other_coroutine
的上下文管理器。
请参阅https://github.com/muhrin/plumpy/blob/8d6cd97d8b521e42f124e77b08bb34c8375cd1b8/plumpy/processes.py#L467了解我如何使用它。
我没有研究过实现,但龙卷风v5切换到使用asyncio
作为默认事件循环,因此它也应与之兼容。