4

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.

bentheiii
  • 451
  • 3
  • 15
  • why not just a value? You just need to increase and decrease it. – Sraw Dec 14 '17 at 08:35
  • @Sraw 1. The real example of what i need is more complicated (hence it requires a stack) 2. Ints aren't thread safe either. – bentheiii Dec 14 '17 at 08:37
  • Actually, as there is a GIL in python, `int` and most of basic objects are thread safe including `list`'s `append` and `pop` methods. – Sraw Dec 14 '17 at 08:41
  • PS: confirmed on linux's cython, not sure about windows. – Sraw Dec 14 '17 at 08:41
  • @Sraw yes but if I enter two "contexts" of the int before i exit another one asyncronusly (i.e. increment and decrement asyncronously), bugs will happen. – bentheiii Dec 14 '17 at 08:52
  • If you really need to use stack, you can use `list` with its `append` and `pop` methods to simulate a stack. Don't worry, they are thread safe. ref: https://docs.python.org/3/tutorial/datastructures.html#using-lists-as-stacks – Sraw Dec 14 '17 at 08:58
  • That's what I'm doing. But if a `conManager` exists a context in async, it might pop a value left by another coroutine, making it unsafe, since there's no guarantee that the first context left is the last entered. – bentheiii Dec 14 '17 at 09:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/161177/discussion-between-bentheiii-and-sraw). – bentheiii Dec 14 '17 at 09:03

1 Answers1

0

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}')
bentheiii
  • 451
  • 3
  • 15