2013-10-26 2 views
3

Мне нужно сгенерировать класс, который бы имитировал набор методов другого класса и вел себя как последний через прокси. например Если Base класс, чтобы имитировать и Deleguate класс, который должен действовать как Base то:Использование закрытия для украшения методов класса

b = Base(args) 
b.any_function() 

строго эквивалентно

d = Deleguate(b) 
d.any_function() 

Если Deleguate использует функцию, которая уже существует в Base, то не будет перезаписана. Это поведение, которое вы ожидаете от наследования и переопределения метода. Наследование не является вариантом в контексте проекта, над которым я работаю (среди других ограничений у меня нет доступа к заводскому коду). И это осложняет ситуацию.

поэтому я решил закодировать «прокси» декоратор:

import inspect 

def proxy(bridge, target): 
    def proxyfy(cls): 
     for _, func in inspect.getmembers(target, predicate=inspect.ismethod): 
      fname = func.__name__ 
      if fname in cls.__dict__: 
       print 'ignoring %s.%s' % (cls, fname) 
       continue 
      print 'adding %s.%s' % (cls, fname) 
      def proxy_func(self, *args, **kwargs): 
       print 'calling %s.%s.%s' % (cls, bridge, fname) 
       bridge_member = getattr(self, bridge) 
       return getattr(bridge_member, fname)(*args, **kwargs) 
      setattr(cls, fname, proxy_func) 
     return cls 
    return proxyfy 

class Base(object): 
    def __init__(self, i): 
     self._i = i 

    def __bar(self): 
     print 0 

    def foo(self): 
     print self._i 

    def foo2(self): 
     print 2 * self._i 


@proxy('_proxy', Base) 
class Deleguate(object): 
    def __init__(self, base): 
     self._proxy = base 

    def foo2(self): 
     print 4 * self._proxy._i 

d = Deleguate(Base(1)) 
d.__bar() # d._proxy.__bar() 
d.foo() # d._proxy.foo() 
d.foo2() # d.foo2() 

я получаю следующий результат:

adding <class '__main__.Deleguate'>.__bar 
ignoring <class '__main__.Deleguate'>.__init__ 
adding <class '__main__.Deleguate'>.foo 
ignoring <class '__main__.Deleguate'>.foo2 
calling <class '__main__.Deleguate'>._proxy.foo2 
2 
calling <class '__main__.Deleguate'>._proxy.foo2 
2 
4 

Я думал, что setattr(cls, fname, proxy_func) назначит новое замыкание, но аргументы будут перезаписаны на каждом шаге цикла и сохраняются только аргументы последней функции foo2. Поэтому при вызове любой «сгенерированной» функции от Deleguate используются аргументы foo2 ...

Почему переменные замыкания перезаписываются? Есть ли способ создать такой прокси-код? Ожидаемый результат:

adding <class '__main__.Deleguate'>.__bar 
ignoring <class '__main__.Deleguate'>.__init__ 
adding <class '__main__.Deleguate'>.foo 
ignoring <class '__main__.Deleguate'>.foo2 
calling <class '__main__.Deleguate'>._proxy.__bar 
0 
calling <class '__main__.Deleguate'>._proxy.foo 
1 
4 
+0

'fname' в' proxy_func() 'является * закрытием *; он не просматривается до тех пор, пока вы не назовете 'proxy_func()', после чего его значение по-прежнему привязано к последнему значению (в этом случае будет 'foo2') **, а не ** значение, с которым оно было связано, когда вы создали вложенная функция. –

+0

Обходной путь - создать локальную переменную где-нибудь, чтобы связать значение в цикле; отдельная фабричная функция сделает это или предоставив 'proxy_func()' аргумент ключевого слова, который связывает 'fname' по умолчанию с объектом функции. –

+0

@MartijnPieters Я сделал это. Но он терпит неудачу с 'AttributeError: 'Base' объект не имеет атрибута '__bar'' на' d .__ bar() # d._proxy .__ bar() '. : '( – thefourtheye

ответ

0

Функции создания замыкания, петли не делают. Имя переменной fname является локальной переменной в proxyfy. Вложенная функция proxy_func относится к этой локальной переменной. Но в то время, когда вложенная функция вызывается, for-loop

for _, func in inspect.getmembers(target, predicate=inspect.ismethod): 

завершена, и локальная переменная fname ссылается на последнее значение в конце цикла, что случается 'foo2'. Итак, независимо от того, какой метод вы вызываете, каждый proxy_func заканчивает вызов foo2.

Чтобы связать разные значения fname с каждым proxy_func, вы можете использовать новый параметр ключевого слова bname со значением по умолчанию. Значение по умолчанию привязано к функции на времени определения не во время выполнения функции. Так что, если использование

for bname, func in inspect.getmembers(target, predicate=inspect.ismethod): 

и использовать bname в качестве значения по умолчанию:

 def proxy_func(self, bname=bname, *args, **kwargs): 

тогда каждый proxy_func будет вызывать соответствующую bname.

Таким образом, с минимальными изменениями в коде, вы можете добавить ключевое слово параметр со значением по умолчанию для proxy_func помнить имя текущего метода:

def proxy(bridge, target): 
    def proxyfy(cls): 
     for bname, func in inspect.getmembers(target, predicate=inspect.ismethod): 
      fname = func.__name__ 
      if fname in cls.__dict__: 
       print 'ignoring %s.%s' % (cls, fname) 
       continue 
      print 'adding %s.%s' % (cls, fname) 
      def proxy_func(self, bname=bname, *args, **kwargs): 
       print 'calling %s.%s.%s' % (cls, bridge, bname) 
       bridge_member = getattr(self, bridge) 
       return getattr(bridge_member, bname)(*args, **kwargs) 
      setattr(cls, fname, proxy_func) 
     return cls 
    return proxyfy 

Однако, я думаю, с помощью __getattr__ может быть проще:

def proxy(bridge): 
    def proxyfy(cls): 
     def __getattr__(self, attr): 
      target = getattr(self, bridge) 
      if attr.startswith('__') and not attr.endswith('__'): 
       # unmangle 
       attr = '_{}{}'.format(type(target).__name__, attr) 
      return getattr(target, attr) 
     setattr(cls, '__getattr__', __getattr__) 
     return cls 
    return proxyfy 

Вот работоспособный пример:

import inspect 

def proxy(bridge, target): 
    def proxyfy(cls): 
     for bname, func in inspect.getmembers(target, predicate=inspect.ismethod): 
      fname = func.__name__ 
      if fname in cls.__dict__: 
       print 'ignoring %s.%s' % (cls, fname) 
       continue 
      print 'adding %s.%s' % (cls, fname) 
      def proxy_func(self, bname=bname, *args, **kwargs): 
       print 'calling %s.%s.%s' % (cls, bridge, bname) 
       bridge_member = getattr(self, bridge) 
       return getattr(bridge_member, bname)(*args, **kwargs) 
      setattr(cls, fname, proxy_func) 
     return cls 
    return proxyfy 

def proxy(bridge): 
    def proxyfy(cls): 
     def __getattr__(self, attr): 
      target = getattr(self, bridge) 
      if attr.startswith('__') and not attr.endswith('__'): 
       # unmangle 
       attr = '_{}{}'.format(type(target).__name__, attr) 
      return getattr(target, attr) 
     setattr(cls, '__getattr__', __getattr__) 
     return cls 
    return proxyfy 

class Base(object): 
    def __init__(self, i): 
     self._i = i 

    def __bar(self): 
     print 0 

    def foo(self): 
     print self._i 

    def foo2(self): 
     print 2 * self._i 


# @proxy('_proxy', Base) 
@proxy('_proxy') 
class Delegate(object): 
    def __init__(self, base): 
     self._proxy = base 

    def foo2(self): 
     print 4 * self._proxy._i 

d = Delegate(Base(1)) 
d.__bar() # d._proxy.__bar() 
d.foo() # d._proxy.foo() 
d.foo2() # d.foo2() 
+0

Спасибо! Ваше первое предложение суммирует все это. Кроме того, вы сделали различие между 'bname' и' fname', что имеет значение при игре с искаженными методами. – user2221662