Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple ways to invoke context manager in python


Background

I have a class in python that takes in a list of mutexes. It then sorts that list, and uses __enter__() and __exit__() to lock/unlock all of the mutexes in a specific order to prevent deadlocks.

The class currently saves us a lot of hassle with potential deadlocks, as we can just invoke it in an RAII style, i.e.:

self.lock = SuperLock(list_of_locks)
# Lock all mutexes.
with self.lock:
    # Issue calls to all hardware protected by these locks.

Problem

We'd like to expose ways for this class to provide an RAII-style API so we can lock only half of the mutexes at once, when called in a certain way, i.e.:

self.lock = SuperLock(list_of_locks)
# Lock all mutexes.
with self.lock:
    # Issue calls to all hardware protected by these locks.

# Lock the first half of the mutexes in SuperLock.list_of_locks
with self.lock.first_half_only:
    # Issue calls to all hardware protected by these locks.

# Lock the second half of the mutexes in SuperLock.list_of_locks
with self.lock.second_half_only:
    # Issue calls to all hardware protected by these locks.

Question

Is there a way to provide this type of functionality so I could invoke with self.lock.first_half_only or with self.lock_first_half_only() to provide a simple API to users? We'd like to keep all this functionality in a single class.

Thank you.

like image 832
Cloud Avatar asked Nov 29 '18 20:11

Cloud


People also ask

Which of the following way can be used to create custom context managers in Python?

You can also create custom function-based context managers using the contextlib. contextmanager decorator from the standard library and an appropriately coded generator function.

Which methods are required to implement context management protocol?

Context managers can be written using classes or functions(with decorators). Creating a Context Manager: When creating context managers using classes, user need to ensure that the class has the methods: __enter__() and __exit__().

Which methods are invoked on Entring to and exiting from the block of code written in with statement?

The block of code which uses the acquired resource is placed inside the block of the with statement. As soon as the code inside the with block is executed, the __exit__() method is called. All the acquired resources are released in the __exit__() method. This is how we use the with statement with user defined objects.


1 Answers

Yes, you can get this interface. The object that will be entered/exited in context of a with statement is the resolved attribute. So you can go ahead and define context managers as attributes of your context manager:

from contextlib import ExitStack  # pip install contextlib2
from contextlib import contextmanager

@contextmanager
def lock(name):
    print("entering lock {}".format(name))
    yield
    print("exiting lock {}".format(name))

@contextmanager
def many(contexts):
    with ExitStack() as stack:
        for cm in contexts:
            stack.enter_context(cm)
        yield

class SuperLock(object):

    def __init__(self, list_of_locks):
        self.list_of_locks = list_of_locks

    def __enter__(self):
        # implement for entering the `with self.lock:` use case
        return self

    def __exit__(self, exce_type, exc_value, traceback):
        pass

    @property
    def first_half_only(self):
        return many(self.list_of_locks[:4])

    @property
    def second_half_only(self):
        # yo dawg, we herd you like with-statements
        return many(self.list_of_locks[4:])

When you create and return a new context manager, you may use state from the instance (i.e. self).

Example usage:

>>> list_of_locks = [lock(i) for i in range(8)] 
>>> super_lock = SuperLock(list_of_locks) 
>>> with super_lock.first_half_only: 
...     print('indented') 
...   
entering lock 0
entering lock 1
entering lock 2
entering lock 3
indented
exiting lock 3
exiting lock 2
exiting lock 1
exiting lock 0

Edit: class based equivalent of the lock generator context manager shown above

class lock(object):

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("entering lock {}".format(self.name))
        return self

    def __exit__(self, exce_type, exc_value, traceback):
        print("exiting lock {}".format(self.name))
        # If you want to handle the exception (if any), you may use the
        # return value of this method to suppress re-raising error on exit
like image 137
wim Avatar answered Oct 20 '22 06:10

wim