Python:如何创建一个并发安全的可重入上下文管理器,在每个上下文中都不同。

4
我希望有一个名为conManager的对象,它是一个可重入上下文管理器实例,每当我进入和退出它的上下文时,它将打印一个数字,但该数字必须比先前上下文的数字高1(从0开始)。
例如:
with conManager:
    print("Afirst")
    with conManager:
        print("Asecond")
        with conManager:
            print("third")
        print("Bsecond")
    print("Bfirst")

期望输出:

0
Afirst
1
Asecond
2
third
2
Bsecond
1
Bfirst
0

到目前为止,我唯一的解决方案是一个带有堆栈的类,但它不是并发安全的。有没有任何并发安全的解决方案?
编辑:正如Sraw所指出的那样,我说线程安全时实际上是并发安全,已相应更改问题。

为什么不只是一个值呢?你只需要增加和减少它。 - Sraw
@Sraw 1. 我需要的真实示例更加复杂(因此需要使用堆栈)2. 整数也不是线程安全的。 - bentheiii
实际上,由于Python中存在GIL,因此int和大多数基本对象都是线程安全的,包括listappendpop方法。 - Sraw
@Sraw 是的,但是如果在退出另一个异步上下文之前异步地输入两个 int 的“上下文”(即异步地增加和减少),将会出现错误。 - bentheiii
这就是我正在做的事情。但是如果在异步上下文中存在一个conManager,它可能会弹出另一个协程留下的值,这使得它不安全,因为不能保证第一个上下文离开是最后一个进入的。 - bentheiii
显示剩余3条评论
1个回答

0
我能想到的唯一解决方案就是覆盖conManager的_call_方法,使得返回一个上下文管理器,但我宁愿采用更清晰、无调用使用的方式。
编辑:既然有其他人表现出了兴趣,我想我应该发布我找到的解决方案。
from contextvars import ContextVar
from random import randrange
from weakref import WeakKeyDictionary

context_stack = ContextVar('context_stack')

class ReCm:
    def __init__(self):
        if not context_stack.get(None):
            context_stack.set(WeakKeyDictionary())
        context_stack.get()[self] = []
    def __enter__(self):
        v = randrange(10)
        context_stack.get()[self].append(v)
        print(f'entered {v}')
        return v
    def __exit__(self, exc_type, exc_val, exc_tb):
        v = context_stack.get()[self].pop()
        print(f'exited {v}')

1
__opcall__是什么? - georgexsh
从来没有想过更好的解决方案吗?太糟糕了。 - Alex Davies

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接