Search code examples
pythondecoratorpython-decorators

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


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?


Solution

  • 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: