2009-08-11 30 views
89

Si può scrivere STH come:decoratori Python nelle classi

class Test(object): 
    def _decorator(self, foo): 
     foo() 

    @self._decorator 
    def bar(self): 
     pass 

Questo fallisce: auto in @self è sconosciuta

Ho anche provato:

@Test._decorator(self) 

che non riesce anche: test sconosciuta

Se si desidera temp. modificare alcune variabili di istanza nel decoratore e eseguire il metodo decorato prima del cambiandole indietro.

Grazie.

risposta

52

Quello che vuoi fare non è possibile. Prendiamo, per esempio, se il codice qui sotto sembra valida:

class Test(object): 

    def _decorator(self, foo): 
     foo() 

    def bar(self): 
     pass 
    bar = self._decorator(bar) 

E, naturalmente, non è valido in quanto self non è definito in quel punto. Lo stesso vale per Test in quanto non sarà definito fino a quando non viene definita la classe stessa (che è nel processo di). Ti sto mostrando questo frammento di codice perché questo è ciò che trasforma lo snippet del tuo decoratore.

Quindi, come potete vedere, l'accesso all'istanza in un decoratore del genere non è realmente possibile poiché i decoratori sono applicati durante la definizione di qualsiasi funzione/metodo a cui sono collegati e non durante l'istanziazione.

Se avete bisogno di l'accesso a livello di classe, provate questo:

class Test(object): 

    @classmethod 
    def _decorator(cls, foo): 
     foo() 

    def bar(self): 
     pass 
Test.bar = Test._decorator(Test.bar) 
+6

"impossibile" è di fatto errata. – zneak

+3

dovrebbe probabilmente essere aggiornato per fare riferimento alla risposta più precisa sotto – natb1

168

Sarebbe qualcosa come questo fare che cosa avete bisogno?

class Test(object): 
    def _decorator(foo): 
     def magic(self) : 
      print "start magic" 
      foo(self) 
      print "end magic" 
     return magic 

    @_decorator 
    def bar(self) : 
     print "normal call" 

test = Test() 

test.bar() 

questo modo si evita la chiamata a sé per accedere al decoratore e lascia nascosto nello spazio dei nomi classe come un metodo regolare.

>>> import stackoverflow 
>>> test = stackoverflow.Test() 
>>> test.bar() 
start magic 
normal call 
end magic 
>>> 

a cura di rispondere alla domanda nei commenti:

Come utilizzare il decoratore nascosta in un'altra classe

class Test(object): 
    def _decorator(foo): 
     def magic(self) : 
      print "start magic" 
      foo(self) 
      print "end magic" 
     return magic 

    @_decorator 
    def bar(self) : 
     print "normal call" 

    _decorator = staticmethod(_decorator) 

class TestB(Test): 
    @Test._decorator 
    def bar(self): 
     print "override bar in" 
     super(TestB, self).bar() 
     print "override bar out" 

print "Normal:" 
test = Test() 
test.bar() 
print 

print "Inherited:" 
b = TestB() 
b.bar() 
print 
+0

Grazie per la risposta. Sì, questo funzionerebbe se non fosse per il fatto che volevo che il decoratore eseguisse alcune operazioni sulle variabili di istanza, e questo richiederebbe il self. – hcvst

+4

Il decoratore o la funzione decorata? Notare che la funzione "magica" restituita che avvolge la barra riceve una variabile auto sopra quando "bar" viene chiamata su un'istanza e potrebbe fare qualsiasi cosa sulle variabili di istanza che voleva prima e dopo (o anche se non lo fosse) chiamata "barra" . Non esistono variabili di istanza quando si dichiara la classe. Volevi fare qualcosa alla classe dal decoratore? Non penso che sia un uso idiomatico. –

+0

Grazie Michael, solo ora ho visto che questo è quello che volevo. – hcvst

2

Ho trovato questa domanda mentre la ricerca di un problema molto simile. La mia soluzione è dividere il problema in due parti. Innanzitutto, è necessario acquisire i dati che si desidera associare ai metodi di classe. In questo caso, handler_for assocerà un comando Unix con il gestore per l'output di quel comando.

Ora che abbiamo associato alcuni dati con ciascun metodo di classe, dobbiamo raccogliere tali dati e memorizzarli in un attributo di classe.

OutputAnalysis.cmd_handler = {} 
for value in OutputAnalysis.__dict__.itervalues(): 
    try: 
     OutputAnalysis.cmd_handler[value.handler_for] = value 
    except AttributeError: 
     pass 
4

Io uso questo tipo di decoratore in alcune situazioni di debug, permette proprietà imperative classe di decorazione, senza dover trovare la funzione chiamante.

class myclass(object): 
    def __init__(self): 
     self.property = "HELLO" 

    @adecorator(property="GOODBYE") 
    def method(self): 
     print self.property 

Ecco il codice decoratore

class adecorator (object): 
    def __init__ (self, *args, **kwargs): 
     # store arguments passed to the decorator 
     self.args = args 
     self.kwargs = kwargs 

    def __call__(self, func): 
     def newf(*args, **kwargs): 

      #the 'self' for a method function is passed as args[0] 
      slf = args[0] 

      # replace and store the attributes 
      saved = {} 
      for k,v in self.kwargs.items(): 
       if hasattr(slf, k): 
        saved[k] = getattr(slf,k) 
        setattr(slf, k, v) 

      # call the method 
      ret = func(*args, **kwargs) 

      #put things back 
      for k,v in saved.items(): 
       setattr(slf, k, v) 

      return ret 
     newf.__doc__ = func.__doc__ 
     return newf 

Nota: perché ho usato un decoratore di classe è necessario utilizzare @adecorator() con le staffe su per decorare le funzioni, anche se non si inoltrano argomenti al costruttore della classe decoratore.

1

pittura sembrano più idonee per modificare la funzionalità di un intero oggetto (compresi oggetti funzione) contro la funzionalità di un metodo oggetto che in genere dipenderà attributi istanza. Per esempio:

def mod_bar(cls): 
    # returns modified class 

    def decorate(fcn): 
     # returns decorated function 

     def new_fcn(self): 
      print self.start_str 
      print fcn(self) 
      print self.end_str 

     return new_fcn 

    cls.bar = decorate(cls.bar) 
    return cls 

@mod_bar 
class Test(object): 
    def __init__(self): 
     self.start_str = "starting dec" 
     self.end_str = "ending dec" 

    def bar(self): 
     return "bar" 

l'output è:

>>> import Test 
>>> a = Test() 
>>> a.bar() 
starting dec 
bar 
ending dec 
0

Potete decorare il decoratore:

import decorator 

class Test(object): 
    @decorator.decorator 
    def _decorator(foo, self): 
     foo(self) 

    @_decorator 
    def bar(self): 
     pass 
2

Questo è un modo che conosco (e ho usato) per accedere self da all'interno di un decoratore definito all'interno della stessa classe:

class Thing(object): 
    def __init__(self, name): 
     self.name = name 

    def debug_name(function): 
     def debug_wrapper(*args): 
      self = args[0] 
      print 'self.name = ' + self.name 
      print 'running function {}()'.format(function.__name__) 
      function(*args) 
      print 'self.name = ' + self.name 
     return debug_wrapper 

    @debug_name 
    def set_name(self, new_name): 
     self.name = new_name 

uscita (testato su pitone 2.7.10):

>>> a = Thing('A') 
>>> a.name 
'A' 
>>> a.set_name('B') 
self.name = A 
running function set_name() 
self.name = B 
>>> a.name 
'B' 

L'esempio precedente è stupido, ma mostra che funziona.

3
class Example(object): 

    def wrapper(func): 
     @functools.wraps(func) 
     def wrap(self, *args, **kwargs): 
      print "inside wrap" 
      return func(self, *args, **kwargs) 
     return wrap 

    @wrapper 
    def method(self): 
     pass 

    wrapper = staticmethod(wrapper) 
1

Ecco un'espansione risposta di Michael Speer per prendere alcuni passi ulteriori:

Un metodo decoratore esempio che prende argomenti e agisce su una funzione con argomenti e un valore restituito.

class Test(object): 
    "Prints if x == y. Throws an error otherwise." 
    def __init__(self, x): 
     self.x = x 

    def _outer_decorator(y): 
     def _decorator(foo): 
      def magic(self, *args, **kwargs) : 
       print("start magic") 
       if self.x == y: 
        return foo(self, *args, **kwargs) 
       else: 
        raise ValueError("x ({}) != y ({})".format(self.x, y)) 
       print("end magic") 
      return magic 

     return _decorator 

    @_outer_decorator(y=3) 
    def bar(self, *args, **kwargs) : 
     print("normal call") 
     print("args: {}".format(args)) 
     print("kwargs: {}".format(kwargs)) 

     return 27 

E poi

In [2]: 

    test = Test(3) 
    test.bar(
     13, 
     'Test', 
     q=9, 
     lollipop=[1,2,3] 
    ) 
    ​ 
    start magic 
    normal call 
    args: (13, 'Test') 
    kwargs: {'q': 9, 'lollipop': [1, 2, 3]} 
Out[2]: 
    27 
In [3]: 

    test = Test(4) 
    test.bar(
     13, 
     'Test', 
     q=9, 
     lollipop=[1,2,3] 
    ) 
    ​ 
    start magic 
    --------------------------------------------------------------------------- 
    ValueError        Traceback (most recent call last) 
    <ipython-input-3-576146b3d37e> in <module>() 
      4  'Test', 
      5  q=9, 
    ----> 6  lollipop=[1,2,3] 
      7) 

    <ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs) 
     11      return foo(self, *args, **kwargs) 
     12     else: 
    ---> 13      raise ValueError("x ({}) != y ({})".format(self.x, y)) 
     14     print("end magic") 
     15    return magic 

    ValueError: x (4) != y (3)