Можно ли создать декоратор @synchronized, который знает об объекте метода?

Я пытаюсь создать оболочку @synchronized, которая создает одну блокировку для каждого объекта и делает вызовы методов потокобезопасными. Я могу сделать это только в том случае, если я могу получить доступ к method.im_self метода в обернутом методе.

    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) Что сбивает с толку, так это то, что параметр func, переданный моему декоратору, меняет тип до того, как он будет передан в генератор-оболочку. Это кажется грубым и ненужным. Почему это происходит?

(2) Есть ли какая-то магия декоратора, с помощью которой я могу вызывать методы для объекта с мьютексом (т.е. одна блокировка для каждого объекта, а не для каждого класса).

ОБНОВЛЕНИЕ: существует множество примеров оберток @synchronized(lock). Однако на самом деле я хочу @synchronized(self). Я могу решить это следующим образом:

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

Однако, поскольку это намного эффективнее, я бы предпочел:

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

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

        return _synchronized

Это возможно?


person user48956    schedule 01.04.2015    source источник


Ответы (3)


Перейти читать:

и в частности:

Затем модуль wrapt содержит описанный там декоратор @synchronized.

Полная реализация достаточно гибкая, чтобы:

@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

Наряду с диспетчером контекста, например, с использованием:

class Object(object):  

    @synchronized
    def function_im_1(self):
        pass  

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

Дополнительную информацию и примеры можно также найти в:

Вы также можете посмотреть доклад на конференции о том, как это реализовано:

person Graham Dumpleton    schedule 02.04.2015
comment
не забудьте использовать decorator из модуля wrapt - person zhy; 21.01.2019

(1) Что сбивает с толку, так это то, что параметр func, переданный моему декоратору, меняет тип до того, как он будет передан в генератор-оболочку. Это кажется грубым и ненужным. Почему это происходит?

Это не так! Скорее, функциональные объекты (и другие дескрипторы) производят свои __get__ результаты, когда вызывается их метод, и этим результатом является объект метода!

Но то, что живет в class __dict__, всегда является дескриптором, в частности, объектом функции! Проверьте это...:

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

Видеть? Никаких объектов метода нигде совсем!-)

Таким образом, во время оформления также не должно быть im_self, и вам нужно будет использовать альтернативную идею, основанную на самоанализе.

person Alex Martelli    schedule 01.04.2015
comment
Спасибо - полезно. Почему язык генерирует несвязанные методы из функций? Почему бы просто не использовать несвязанный метод? Похоже, это также предотвращает синхронизацию для каждого класса. - person user48956; 02.04.2015
comment
@ user48956, в Python 3 (начиная с версии 3.0) есть только связанные методы — несвязанные методы были удалены из языка, так что только два< /i> типы, функции и связанные методы могут сделать все это проще. То есть дизайн еще в древние времена Python 2.0 (15 лет назад) был сложнее, чем нужно. Однако улучшение в 3.0 было гораздо большим упрощением, чем то, что вы предлагаете, и ни в малейшей степени не поможет вам в реализации подхода, который вы бы предпочли (даже если аргумент вашего декоратора был несвязанным метод, его im_self будет None, так что в любом случае никакой помощи!-). - person Alex Martelli; 02.04.2015

Вы не можете получить self во время оформления, потому что декоратор применяется во время определения функции. self еще не существует; на самом деле класса еще не существует.

Если вы хотите сохранить свою блокировку на экземпляре (возможно, это место, где должно быть значение для каждого экземпляра), то это может вам подойти:

def synchronizedMethod(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

Вы также можете сгенерировать блокировку в своем методе __init__() для некоторого базового класса и таким же образом сохранить ее в экземпляре. Это упрощает ваш декоратор, потому что вам не нужно проверять наличие атрибута self._lock.

person kindall    schedule 02.04.2015
comment
Это лучший ответ, ИМО. Это безопаснее и подходит для большего количества вариантов использования, чем декораторы, которые часто используют глобальные блокировки. - person Erik Aronesty; 04.03.2020
comment
Небольшая опечатка, но должно быть hasattr - person Javon; 22.11.2020