Search code examples
pythonconcurrencycontextmanagerreentrancy

Python: How to create a concurrent-safe reentrant context manager which is different in every context


I want to have an object conManager that is a reentrant context manager instance such that whenever I enter and exit its context it will print a number, but the number must be one higher than the number of the previus context (starting at 0).

Example:

with conManager:
    print("Afirst")
    with conManager:
        print("Asecond")
        with conManager:
            print("third")
        print("Bsecond")
    print("Bfirst")

Output expected:

0
Afirst
1
Asecond
2
third
2
Bsecond
1
Bfirst
0

The only solution I have so far is a class with a stack in it, but that's not concurrent-safe. Are there any concurrent-safe solutions?

EDIT: as Sraw pointed out, I said thread safe when I meant concurrent-safe, changed the question accordingly.


Solution

  • The only solution I can think of is overriding conManager's _call_ so that it returns a context manager, but I'd rather have a cleaner, no-call usage.

    EDIT: since someone else showed interest I guess I guess I should post a solution I found

    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}')