2013-06-11 3 views
0

Я ищу общий и простой способ синхронизации методов класса python, который сам по себе не использует асинхронные вызовы. Некоторые возможности приходят мне на ум: Во-первых, использование декораторов на всех методах самого класса: http://code.activestate.com/recipes/577105-synchronization-decorator-for-class-methods/. Но я не хочу менять класс, во-вторых, использование обертки или подкласса, синхронизирующего доступ ко всем методам подкласса/ядра. Я подумал, что, возможно, существует общий способ синхронизации доступа к объекту python, чтобы вы случайно не пропустили метод из суперкласса в иерархии (особенно если он впоследствии изменен). Итак, в-третьих, вы, вероятно, можете использовать общий прокси-сервер, похожий на: http://code.activestate.com/recipes/366254-generic-proxy-object-with-beforeafter-method-hooks/ и использовать блокирующие блокировки для каждого доступа. Я предпочел бы третий вариант. Меня просто беспокоит, что я не нашел для этого рецепта. Есть что-то с этим решением или есть лучший?синхронизировать доступ к объекту python

EDIT2: Последний вариант выглядит следующий фрагмент и был протестирован с codetidy.com/5911/. Тест не является доказательством того, что он работает, просто световая индикация. Поскольку это не мое ежедневное кодирование, это поможет, если кто-то более опытный может проверить, есть ли какие-либо ошибки.

#!/usr/bin/env python 
import types 
from pprint import pformat 
from threading import RLock 

class SynchronizeMethodWrapper: 
    """ 
    Wrapper object for a method to be called. 
    """ 
    def __init__(self, obj, func, name, rlock): 
     self.obj, self.func, self.name = obj, func, name 
     self.rlock = rlock 
     assert obj is not None 
     assert func is not None 
     assert name is not None 

    def __call__(self, *args, **kwds): 
     """ 
     This method gets called before a method is called to sync access to the core object. 
     """ 
     with self.rlock: 
      rval = self.func(*args, **kwds) 
      return rval 


class SynchronizeProxy(object): 
    """ 
    Proxy object that synchronizes access to a core object methods and attributes that don't start with _. 
    """ 
    def __init__(self, core): 
     self._obj = core 
     self.rlock = RLock() 

    def __getattribute__(self, name): 
     """ 
     Return a proxy wrapper object if this is a method call. 
     """ 
     if name.startswith('_'): 
      return object.__getattribute__(self, name) 
     else: 
      att = getattr(self._obj, name) 
      if type(att) is types.MethodType: 
       return SynchronizeMethodWrapper(self, att, name, object.__getattribute__(self, "rlock")) 
      else: 
       return att 

    def __setitem__(self, key, value): 
     """ 
     Delegate [] syntax. 
     """ 
     name = '__setitem__' 
     with self.rlock: 
      att = getattr(self._obj, name) 
      pmeth = SynchronizeMethodWrapper(self, att, name, self.rlock) 
      pmeth(key, value) 

EDIT3: Я пошел с SynchronizeProxy, и это, кажется, работает до сих пор. Поскольку это решение, ближе всего к тому, что мне нужно, я буду выбирать свой ответ в виде раствора

ответ

0

Я пошел с SynchronizeProxy и, похоже, работает до сих пор. Поскольку это решение приближается к тому, что мне нужно, я выберу свой ответ в качестве решения. Если у меня возникнут какие-либо проблемы, я обновлю этот ответ.

#!/usr/bin/env python 
import types 
from pprint import pformat 
from threading import RLock 

class SynchronizeMethodWrapper: 
    """ 
    Wrapper object for a method to be called. 
    """ 
    def __init__(self, obj, func, name, rlock): 
     self.obj, self.func, self.name = obj, func, name 
     self.rlock = rlock 
     assert obj is not None 
     assert func is not None 
     assert name is not None 

    def __call__(self, *args, **kwds): 
     """ 
     This method gets called before a method is called to sync access to the core object. 
     """ 
     with self.rlock: 
      rval = self.func(*args, **kwds) 
      return rval 


class SynchronizeProxy(object): 
    """ 
    Proxy object that synchronizes access to a core object methods and attributes that don't start with _. 
    """ 
    def __init__(self, core): 
     self._obj = core 
     self.rlock = RLock() 

    def __getattribute__(self, name): 
     """ 
     Return a proxy wrapper object if this is a method call. 
     """ 
     if name.startswith('_'): 
      return object.__getattribute__(self, name) 
     else: 
      att = getattr(self._obj, name) 
      if type(att) is types.MethodType: 
       return SynchronizeMethodWrapper(self, att, name, object.__getattribute__(self, "rlock")) 
      else: 
       return att 

    def __setitem__(self, key, value): 
     """ 
     Delegate [] syntax. 
     """ 
     name = '__setitem__' 
     with self.rlock: 
      att = getattr(self._obj, name) 
      pmeth = SynchronizeMethodWrapper(self, att, name, self.rlock) 
      pmeth(key, value) 
1

Вы можете использовать очередь для прокси вызовы к этому классу:

http://pymotw.com/2/Queue/

Update:

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

+0

Можете ли вы рассказать о прокси-вызовах класса с очередью и о том, как это лучше предлагаемых решений? – phobic

+0

Я немного адаптировал свой ответ. Надеюсь, поможет. – pypat

1

Если вам действительно нужно, вы можете использовать черную магию метаклассов python для динамического добавления декоратора к каждому методу класс во время создания класса. Ниже приведен краткий пример того, как вы можете это сделать. Он создает общий метаклас синхронизатора, который затем подкласс создает синхронизаторы для каждого конкретного класса. Наконец, вы подклассифицируете исходный класс, который вы хотите синхронизировать и применить к нему метакласс синхронизации. Примечание. Я использую синтаксис метакласса python 3.

from threading import RLock 

# 
# Generic synchronizer 
# 
class SynchroMeta(type): 

    def __init__(cls, name, bases, dct): 
     super(SynchroMeta, cls).__init__(name, bases, dct) 
     dct['__lock__'] = RLock() 

     def sync_decorator(f): 
      def inner(*args, **kwargs): 
       with dct['__lock__']: 
        print("Synchronized call") 
        return f(*args, **kwargs) 
      return inner 

     for b in bases: 
      if b.__name__ == cls.sync_object_name: 
       for name, obj in b.__dict__.items(): 
        # Synchronize any callables, but avoid special functions 
        if hasattr(obj, '__call__') and not name.startswith('__'): 
         print("Decorating: ", name) 
         setattr(b, name, sync_decorator(obj)) 

# 
# Class you want to synchronize 
# 
class MyClass: 
    def __init__(self, v): 
     self.value = v 

    def print_value(self): 
     print("MyClass.value: ", self.value) 

# 
# Specific synchronizer for "MyClass" type 
# 
class MyClassSynchro(SynchroMeta): 
    sync_object_name = "MyClass" 


# 
# Wrapper that uses the specific synchronizer metaclass 
# 
class MyClassWrapper(MyClass, metaclass=MyClassSynchro): 
    pass 


if __name__ == "__main__": 
    w = MyClassWrapper('hello') 
    w.print_value() 
+0

Если SynchroMeta является метаклассом, насколько я понимаю метаклассы, есть только один «экземпляр». Поэтому только один замок, который также блокирует независимые экземпляры MyClass. Это нормально, но насколько я могу видеть не лучше, чем мой подход, особенно со многими объектами MyClass. – phobic

+0

Также не должен быть реентерабельный замок, так как некоторые методы могут вызывать другой метод того же класса и, следовательно, тупик? – phobic

+1

Да, вы правы. Я обновлю.Также для вашего первого комментария. Должен быть способ добавить блокировку каждого экземпляра. Я думаю, мне нужно будет также переопределить метод __call__ метода метакласса и добавить блокировку для каждого созданного экземпляра. FYI, я не гуру на метаклассах, я знаю достаточно, чтобы быть опасным =). – mshildt

Смежные вопросы