2017-01-01 3 views
1

Ich habe ein Problem mit der Dekoration einer nativen Liste Methoden mit Post-Hook-Funktionen. Ich brauche einen Dekorator, der als Argument eine native Listenmethode annimmt und nach dem Aufruf der Methode auch einen Code der Funktion, der dekoriert wird, ausführen soll. Dieses Stück Code funktioniert in Ordnung:Klassenmethode Hook Dekorator in Python2

def _add_hook(clsmtd, info_fcn): 
    def hooked(s, *p, **k): 
     ret = clsmtd(s, *p, **k) 
     '''some magic happens here''' 
     print info_fcn(s, *p, **k) 
     return ret 
    return hooked 

def reporting_list_deco(cls): 
    def append_info(s, *p, **k): 
     return 'append to %s' % s._name 
    cls.append = _add_hook(cls.append, append_info) 

    def remove_info(s, idx, *p, **k): 
     return 'removin %s[%s]' % (s._name, idx) 
    cls.remove = _add_hook(cls.remove, remove_info) 

    def setitem_info(s, idx, *p, **k): 
     return 'setitem %s[%s]' % (s._name, idx) 
    cls.__setitem__ = _add_hook(cls.__setitem__, setitem_info) 
    ''' and so on also for pop, sort, insert, reverse and extend ''' 
    return cls 

def test_reporting_list(): 
    @reporting_list_deco 
    class reportin_list(list): 
     def __init__(self, *p, **k): 
      super(reportin_list, self).__init__(*p, **k) 
      self._name = 'foo' 
    rl = reportin_list([6,6,6]) 
    rl[1] = 1 
    rl.append(7) 
    rl.remove(1) 
    ''' outputs: 
     setitem foo[1] 
     append to foo 
     removin foo[1] 
    ''' 

Aber ich mag es so schreiben:

def better_reporting_list_deco(cls): 
    @_add_hook_for(cls.append) 
    def append_info(s, *p, **k): 
     return 'append to %s' % s._name 

    @_add_hook_for(cls.remove) 
    def remove_info(s, idx, *p, **k): 
     return 'removin %s[%s]' % (s._name, idx) 

    @_add_hook_for(cls.__setitem__) 
    def setitem_info(s, idx, *p, **k): 
     return 'setitem %s[%s]' % (s._name, idx) 

    return cls 

Das Problem ist, dass ich weiß nicht, wie die _add_hook_for Dekorateur zu schreiben. Bitte um Rat.

+0

Sie könnten es vielleicht als '@_add_hook_for (cls, 'append')', indem es wie eine seltsame Verwendung eines Dekorators scheint. – jonrsharpe

+0

@jonrsharpe: Guter Punkt. Aber fast jede der nativen Methoden hat eine andere Schnittstelle, und Reporting-Funktionen erfordern unterschiedliche Argumente. – Mikaelblomkvistsson

+0

Wird dies immer auf integrierte Typen angewendet? Dann kann ich dies mit dem gleichen Code für Python 2 und 3 arbeiten :-) –

Antwort

3

Schreiben Sie eine Decorator Factory. Sie haben haben, um in der Klasse zu übergeben, wie Sie nicht zuverlässig abrufen können, andernfalls (Sie übergeben Methoden aus einer übergeordneten Klasse, so auch wenn wir es geschafft, den Kontext abrufen wir würden die falsche Zielklasse verwenden):

def _add_hook_for(cls, target): 
    def hook_decorator(hook): 
     def hooked(s, *p, **k): 
      ret = target(s, *p, **k) 
      # some magic happens here 
      print hook(s, *p, **k) 
      return ret 
     setattr(cls, target.__name__, hooked) 
     return hook 
    return hook_decorator 

Ihre Klasse Dekorateur wird dann:

def better_reporting_list_deco(cls): 
    @_add_hook_for(cls, cls.append) 
    def append_info(s, *p, **k): 
     return 'append to %s' % s._name 

    @_add_hook_for(cls, cls.remove) 
    def remove_info(s, idx, *p, **k): 
     return 'removing %s[%s]' % (s._name, idx) 

    @_add_hook_for(cls, cls.__setitem__) 
    def setitem_info(s, idx, *p, **k): 
     return 'setitem %s[%s]' % (s._name, idx) 

    return cls 

Demo:

>>> @better_reporting_list_deco 
... class reporting_list(list): 
...  def __init__(self, *p, **k): 
...   super(reporting_list, self).__init__(*p, **k) 
...   self._name = 'foo' 
... 
>>> rl = reporting_list([6, 6, 6]) 
>>> rl[1] = 1 
setitem foo[1] 
>>> rl.append(7) 
append to foo 
>>> rl.remove(1) 
removing foo[1] 
0

Dank @ martijn-Pieters ich es so neu geschrieben (funktioniert gut in Python 2.7):

def _add_hook_for(cls, target): 
    def hook_decorator(hook): 
     def hooked(s, *p, **k): 
      ret = target(s, *p, **k) 
      # some magic happens here 
      print(hook(s, target.__name__, *p, **k)) 
      return ret 
     setattr(cls, target.__name__, hooked) 
     return hook 
    return hook_decorator 

def reporting_list_deco(cls): 
    @_add_hook_for(cls, cls.append) 
    @_add_hook_for(cls, cls.extend) 
    @_add_hook_for(cls, cls.sort) 
    @_add_hook_for(cls, cls.reverse) 
    def no_idx_info(s, op, *p, **k): 
     return '%s %s' % (op, s._name) 

    @_add_hook_for(cls, cls.__setitem__) 
    @_add_hook_for(cls, cls.insert) 
    @_add_hook_for(cls, cls.__delitem__) 
    @_add_hook_for(cls, cls.remove) 
    @_add_hook_for(cls, cls.pop) 
    def idx_info(s, op, idx='', *p, **k): 
     return '%s %s[%s]' % (op, s._name, idx) 
    return cls 

def test_reporting_list(): 
    @reporting_list_deco 
    class reporting_list(list): 
     def __init__(self, name, *p, **k): 
      super(reporting_list, self).__init__(*p, **k) 
      self._name = name 

    rl = reporting_list('foo', [61,62,63]) 
    rl[1] = 1 
    rl.append(7) 
    rl.remove(1) 
    rl.extend([5,6,7]) 
    rl.pop() 
    rl.pop(2) 
    rl.sort() 
    del rl[0] 
    rl.reverse() 
    print(rl) 

Ausgänge:

__setitem__ foo[1] 
append foo 
remove foo[1] 
extend foo 
pop foo[] 
pop foo[2] 
sort foo 
__delitem__ foo[0] 
reverse foo 
[63, 61, 6] 

Es kann auch zu einem dict angewandt werden. Danke Martijn!