Possible to create a @synchronized decorator that's aware of a method's object?

user48956 picture user48956 · Apr 2, 2015 · Viewed 8.6k times · Source

I'm trying to create a @synchronized wrapper that creates one Lock per object and makes method calls thread safe. I can only do this if I can access method.im_self of the method in the wrapped method.

    class B:
        def f(self): pass

    assert inspect.ismethod( B.f ) # OK
    assert inspect.ismethod( B().f ) # OK
    print B.f    # <unbound method B.f>
    print B().f  # <bound method B.f of <__main__.B instance at 0x7fa2055e67e8>>



    def synchronized(func):
        # func is not bound or unbound!
        print func  # <function f at 0x7fa20561b9b0>    !!!!

        assert inspect.ismethod(func)  # FAIL
        # ... allocate one lock per C instance
        return func

    class C:
        @synchronized
        def f(self): pass

(1) What's confusing is that the func parameter passed to my decorator changes type before it gets passed into the wrapper-generator. This seem is rude and unnecessary. Why does this happen?

(2) Is there some decorator magic by which I can make method calls to an object mutex-ed (i.e. one lock per object, not per class).

UPDATE: There are many examples of @synchronized(lock) wrappers. However, really what I want is @synchronized(self). I can solve it like this:

    def synchronizedMethod(func):
        def _synchronized(*args, **kw):
             self = args[0]
             lock = oneLockPerObject(self)
             with lock: return func(*args, **kw)
        return _synchronized

However, because its much more efficient, I'd prefer:

    def synchronizedMethod(func):
        lock = oneLockPerObject(func.im_self)

        def _synchronized(*args, **kw):
             with lock: return func(*args, **kw)

        return _synchronized

Is this possible?

Answer

Graham Dumpleton picture Graham Dumpleton · Apr 2, 2015

Go read:

and in particular:

The wrapt module then contains the @synchronized decorator described there.

The full implementation is flexible enough to do:

@synchronized # lock bound to function1
def function1():
    pass 

@synchronized # lock bound to function2
def function2():
    pass 

@synchronized # lock bound to Class
class Class(object):  

    @synchronized # lock bound to instance of Class
    def function_im(self):
        pass 

    @synchronized # lock bound to Class
    @classmethod
    def function_cm(cls):
        pass

    @synchronized # lock bound to function_sm
    @staticmethod
    def function_sm():
        pass

Along with context manager like usage as well:

class Object(object):  

    @synchronized
    def function_im_1(self):
        pass  

    def function_im_2(self):
        with synchronized(self):
            pass

Further information and examples can also be found in:

There is also a conference talk you can watch on how this is implemented at: