2017-09-20 4 views
0

Ich versuche, einen Dekorator zu schreiben, der sowohl zu Instanzmethoden als auch zu Nicht-Instanzmethoden hinzugefügt werden kann. Ich habe meinen Code auf ein Minimum Beispiel reduziert, dass mein PunktDekorator, der sich selbst

def call(fn): 
    def _impl(*args, **kwargs): 
     return fn(*args, **kwargs) 

    fn.call = _impl 

    return fn 

class Foo(object): 
    @call 
    def bar(self): 
     pass 

Foo().bar.call() 

Traceback (most recent call last): 
    File "/tmp/511749370/main.py", line 14, in <module> 
    Foo().bar.call() 
    File "/tmp/511749370/main.py", line 3, in _impl 
    return fn(*args, **kwargs) 
TypeError: bar() missing 1 required positional argument: 'self' 

Ist es möglich, etwas zu tun, wie dies ohne auf

Foo.bar.call(Foo()) 

Dies gibt dem schönen Fehler zeigt, Oder ist das meine einzige Option?

+0

Schauen Sie sich die Lösung bei: https://stackoverflow.com/questions/7590682/access-self-from-decorator. Ich weiß, es ist nicht das exakt gleiche Problem, aber es könnte hilfreich sein. – Idan

+0

@IdanMeyer Ich habe versucht, "self" als Parameter zu '_impl' hinzuzufügen, aber das hat den Fehler einfach auf' _impl() verschoben, das 1 benötigtes Positionsargument: 'self', und ich habe es mit 'fn .__ self__' versucht löst einen 'AttributeError' aus. –

Antwort

2

Sie müssen Ihren Dekorateur als Klasse implementieren und die descriptor protocol implementieren. Grundsätzlich ist der Deskriptor Funktion, was für das Erstellen gebundener Methoden verantwortlich ist. Durch Überschreiben dieser Funktion erhalten Sie Zugriff auf self und können eine gebundene Kopie der call-Funktion erstellen.

Die folgende Implementierung tut genau das. Die Foo-Instanz wird im Attribut __self__ gespeichert. Der Decorator hat eine __call__-Methode, die die verzierte Funktion aufruft, und eine call-Methode, die dasselbe tut.

import inspect 
import functools 
from copy import copy 

class call: 
    def __init__(self, func): 
     self.func = func 
     self.__self__ = None # "__self__" is also used by bound methods 

    def __call__(self, *args, **kwargs): 
     # if bound to on object, pass it as the first argument 
     if self.__self__ is not None: 
      args = (self.__self__,) + args 

     return self.func(*args, **kwargs) 

    def call(self, *args, **kwargs): 
     self(*args, **kwargs) 

    def __get__(self, obj, cls): 
     if obj is None: 
      return self 

     # create a bound copy of the decorator 
     bound = copy(self) 
     bound.__self__ = obj 

     # update __doc__ and similar attributes 
     functools.wraps(bound.func)(bound) 
     bound.__signature__ = inspect.signature(bound.func) 

     # add the bound instance to the object's dict so that 
     # __get__ won't be called a 2nd time 
     setattr(obj, self.func.__name__, bound) 

     return bound 

Test:

class Foo(object): 
    @call 
    def bar(self): 
     print('bar') 

@call 
def foo(): 
    print('foo') 

Foo().bar.call() # output: bar 
foo() # output: foo 
+0

Es ist nicht besonders schön, aber es funktioniert - danke! –