2009-03-13 9 views
14

Se ho una funzione:È possibile sostituire un decoratore di funzione/metodo in fase di esecuzione? [Python]


@aDecorator 
def myfunc1(): 
    # do something here 

if __name__ = "__main__": 
    # this will call the function and will use the decorator @aDecorator 
    myfunc1() 
    # now I want the @aDecorator to be replaced with the decorator @otherDecorator 
    # so that when this code executes, the function no longer goes through 
    # @aDecorator, but instead through @otherDecorator. How can I do this? 
    myfunc1() 

E 'possibile sostituire un decoratore in fase di esecuzione?

risposta

2

Non so se c'è un modo per "sostituire" un decoratore una volta applicato, ma suppongo che probabilmente non ci sia, perché la funzione è già stata cambiata.

Si potrebbe, in ogni caso, applicare una decoratrice in fase di esecuzione sulla base di alcune condizioni:

#!/usr/bin/env python 

class PrintCallInfo: 
    def __init__(self,f): 
     self.f = f 
    def __call__(self,*args,**kwargs): 
     print "-->",self.f.__name__,args,kwargs 
     r = self.f(*args,**kwargs) 
     print "<--",self.f.__name__,"returned: ",r 
     return r 

# the condition to modify the function... 
some_condition=True 

def my_decorator(f): 
    if (some_condition): # modify the function 
     return PrintCallInfo(f) 
    else: # leave it as it is 
     return f 

@my_decorator 
def foo(): 
    print "foo" 

@my_decorator 
def bar(s): 
    print "hello",s 
    return s 

@my_decorator 
def foobar(x=1,y=2): 
    print x,y 
    return x + y 

foo() 
bar("world") 
foobar(y=5) 
-1

Se il decoratore è una funzione, è sufficiente sostituirlo.

aDecorator = otherDecorator 
+1

Ma questo non cambierà myfunc1(). –

+0

Ho provato a utilizzare il codice, la funzione viene comunque eseguita con il decoratore iniziale. – Geo

+0

wow .. non mi aspettavo che succedesse. – miya

0

Se si desidera cambiare esplicitamente il decoratore, si potrebbe anche scegliere un approccio più esplicito invece di creare una funzione decorato:

deco1(myfunc1, arg1, arg2) 
deco2(myfunc1, arg2, arg3) 

deco1() e deco2() si applicherebbe la funzionalità i decoratori forniscono e chiamano myfunc1() con gli argomenti.

+0

Sì, avrei potuto usare una funzione che accetta una funzione poichè è la prima param, la mia funzione come secondo param, seguita dagli argomenti e ha un modo piuttosto generico di farlo.Mi interessava comunque, se posso sostituire un decoratore con il mio. Mi piacerebbe "giocare" con il comportamento delle altre classi – Geo

13

Come menzionato da Miya, è possibile sostituire il decoratore con un'altra funzione prima che l'interprete raggiunga la dichiarazione di funzione. Tuttavia, una volta applicato il decoratore alla funzione, non penso ci sia un modo per sostituire dinamicamente il decoratore con uno diverso. Ad esempio:

@aDecorator 
def myfunc1(): 
    pass 

# Oops! I didn't want that decorator after all! 

myfunc1 = bDecorator(myfunc1) 

Non funziona, perché myfunc1 non è più la funzione che è stata originariamente definita; è già stato avvolto. L'approccio migliore è quello di applicare manualmente il decoratori, oldskool-style, cioè:

def myfunc1(): 
    pass 

myfunc2 = aDecorator(myfunc1) 
myfunc3 = bDecorator(myfunc1) 

Edit: O, per essere un po 'più chiaro,

def _tempFunc(): 
    pass 

myfunc1 = aDecorator(_tempFunc) 
myfunc1() 
myfunc1 = bDecorator(_tempFunc) 
myfunc1() 
+0

+1: aDecorate (func1) e bDecorate (func1) –

3

Ecco una formidabile recipe to get you started. Fondamentalmente, l'idea è di passare un'istanza di classe nel decoratore. È quindi possibile impostare gli attributi sull'istanza della classe (renderlo un Borg se lo si desidera) e utilizzarlo per controllare il comportamento del decoratore stesso.

Ecco un esempio:

class Foo: 
    def __init__(self, do_apply): 
     self.do_apply = do_apply 

def dec(foo): 
    def wrap(f): 
     def func(*args, **kwargs): 
      if foo.do_apply: 
       # Do something! 
       pass 
      return f(*args, **kwargs) 
     return func 
    return wrap 

foo = Foo(False) 
@dec(foo) 
def bar(x): 
    return x 

bar('bar') 
foo.do_apply = True 
# Decorator now active! 
bar('baz') 

Naturalmente, è anche possibile incorporare il "decoratore decoratore" per preservare le firme, ecc

1

Certo - si può ottenere l'oggetto funzione e fare quello che vuoi con esso:

# Bypass a decorator 

import types 

class decorator_test(object): 

    def __init__(self, f): 
     self.f = f 

    def __call__(self): 
     print "In decorator ... entering: ", self.f.__name__ 
     self.f() 
     print "In decorator ... exiting: ", self.f.__name__ 


@decorator_test 
def func1(): 
    print "inside func1()" 

print "\nCalling func1 with decorator..." 
func1() 

print "\nBypassing decorator..." 
for value in func1.__dict__.values(): 
    if isinstance(value, types.FunctionType) and value.func_name == "func1": 
     value.__call__() 
+0

Questo assume il controllo sul decoratore iniziale. Immagina il decoratore 'def aDecorator (f): return lambda * args, ** kwargs:" HAHAHAHAHAHAHA "'. In generale, questo non è vicino a un approccio valido. Se __do__ capita di avere il controllo del decoratore iniziale, allora ci sono modi più puliti e più idiomatici per farlo. – aaronasterling

0

I so che è un thread vecchio, ma mi sono divertito a farlo

def change_deco(name, deco, placeholder=' #'): 
with open(name + '.py', 'r') as file: 
    lines = file.readlines() 
for idx, string in enumerate(lines): 
    if placeholder in string and repr(placeholder) not in string: 
     lines[idx] = f' @{deco}\r\n' 
exec(''.join(lines)) 
return locals()[name] 
Problemi correlati