Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 926
user48956 Avatar asked Apr 01 '15 23:04

user48956


3 Answers

Go read:

  • https://github.com/GrahamDumpleton/wrapt/tree/develop/blog

and in particular:

  • https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/07-the-missing-synchronized-decorator.md
  • https://github.com/GrahamDumpleton/wrapt/blob/develop/blog/08-the-synchronized-decorator-as-context-manager.md

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

  • https://pypi.python.org/pypi/wrapt

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:

  • http://wrapt.readthedocs.org/en/latest/examples.html

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

  • https://www.youtube.com/watch?v=EB6AH-85zfY&t=1s
like image 59
Graham Dumpleton Avatar answered Oct 26 '22 23:10

Graham Dumpleton


(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?

It doesn't! Rather, function objects (and other descriptors) produce their __get__'s results when that method of theirs is called -- and that result is the method object!

But what lives in the class's __dict__ is always the descriptor -- specifically, the function object! Check it out...:

>>> class X(object):
...   def x(self): pass
... 
>>> X.__dict__['x']
<function x at 0x10fe04e60>
>>> type(X.__dict__['x'])
<type 'function'>

See? No method objects around anywhere at all!-)

Therefore, no im_self around either, at decoration time -- and you'll need to go with your introspection-based alternative idea.

like image 29
Alex Martelli Avatar answered Oct 26 '22 23:10

Alex Martelli


You can't get self at decoration time because the decorator is applied at function definition time. No self exists yet; in fact, the class doesn't exist yet.

If you're willing to store your lock on the instance (which is arguably where a per-instance value should go) then this might do ya:

def synchronized_method(func):
    def _synchronized(self, *args, **kw):
         if not hasattr(self, "_lock"): self._lock = oneLockPerObject(self)
         with self._lock: return func(self, *args, **kw)
    return _synchronized

You could also generate the lock in your __init__() method on a base class of some sort, and store it on the instance in the same way. That simplifies your decorator because you don't have to check for the existence of the self._lock attribute.

like image 42
kindall Avatar answered Oct 26 '22 23:10

kindall