Python: как создать параллельный безопасный реентерабельный менеджер контекста, который отличается в каждом контексте

Я хочу иметь объект conManager, который является реентерабельным диспетчером контекста такой экземпляр, что всякий раз, когда я вхожу и выхожу из его контекста, он будет печатать число, но число должно быть на единицу больше, чем число предыдущего контекста (начиная с 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

Единственное решение, которое у меня есть до сих пор, - это класс со стеком в нем, но это не является параллельным. Существуют ли параллельные безопасные решения?

РЕДАКТИРОВАТЬ: как указал Сроу, я сказал потокобезопасный, когда имел в виду параллельный безопасный, соответственно изменил вопрос.


person bentheiii    schedule 14.12.2017    source источник
comment
почему не просто значение? Вам просто нужно увеличить и уменьшить его.   -  person Sraw    schedule 14.12.2017
comment
@Sraw 1. Реальный пример того, что мне нужно, более сложен (следовательно, для него требуется стек) 2. Целые числа также не являются потокобезопасными.   -  person bentheiii    schedule 14.12.2017
comment
На самом деле, поскольку в python есть GIL, int и большинство базовых объектов являются потокобезопасными, включая методы append и pop из list.   -  person Sraw    schedule 14.12.2017
comment
PS: подтверждено на Linux cython, не уверен насчет окон.   -  person Sraw    schedule 14.12.2017
comment
@Sraw да, но если я ввожу два контекста int до того, как асинхронно выйду из другого (т. Е. Асинхронно увеличиваю и уменьшаю), возникнут ошибки.   -  person bentheiii    schedule 14.12.2017
comment
Если вам действительно нужно использовать стек, вы можете использовать list с его методами append и pop для имитации стека. Не волнуйтесь, они потокобезопасны. ссылка: docs.python.org/3/tutorial/   -  person Sraw    schedule 14.12.2017
comment
Это то, что я делаю. Но если conManager существует контекст в асинхронном режиме, он может извлечь значение, оставленное другой сопрограммой, что сделает его небезопасным, поскольку нет гарантии, что первый оставшийся контекст является последним введенным.   -  person bentheiii    schedule 14.12.2017
comment
Давайте продолжим это обсуждение в чате.   -  person bentheiii    schedule 14.12.2017


Ответы (1)


Единственное решение, которое я могу придумать, это переопределить _вызов_ conManager, чтобы он возвращал диспетчер контекста, но я бы предпочел более чистое использование без вызова.

РЕДАКТИРОВАТЬ: поскольку кто-то еще проявил интерес, я думаю, я должен опубликовать решение, которое я нашел

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}')
person bentheiii    schedule 14.12.2017
comment
что такое __opcall__? - person georgexsh; 14.12.2017
comment
Никогда не придумали лучшего решения для этого? Это очень плохо - person Alex Davies; 01.09.2020