2012-05-16 12 views
9

La libreria standard Python ha una scorciatoia per scrivere decoratori che accettano argomenti?Python: scorciatoia per scrivere decoratori che accettano argomenti?

Per esempio, se voglio scrivere un decoratore come with_timeout(timeout):

@with_timeout(10.0) 
def cook_eggs(eggs): 
    while not eggs.are_done(): 
     eggs.cook() 

devo scrivere qualcosa di simile a:

def with_timeout(timeout): 
    _func = [None] 
    def with_timeout_helper(*args, **kwargs): 
     with Timeout(timeout): 
      return _func[0](*args, **kwargs) 
    def with_timeout_return(f): 
     return functools.wraps(f)(with_timeout_helper) 
    return with_timeout_return 

Ma questo è terribilmente prolisso. C'è una scorciatoia che rende i decoratori che accettano argomenti più facili da scrivere?

Nota: Mi rendo conto che è anche possibile utilizzare tre funzioni annidate per implementare decoratori con argomenti ... Ma che si sente solo un po 'troppo non ottimale.

Per esempio, forse qualcosa di simile a una funzione @decorator_with_arguments:

@decorator_with_arguments 
def timeout(f, timeout): 
    @functools.wraps(f) 
    def timeout_helper(*args, **kwargs): 
     with Timeout(timeout): 
      return f(*args, **kwargs) 
    return timeout_helper 
+0

Se avete bisogno di più aiuto decoratori e annotazioni vedere il mio post sul blog qui. http://blog.mattalcock.com/2013/1/5/decorates-and-annotations/ –

risposta

7

io tendo a scrivere le mie decoratori come classi ad essere onesti

class TestWithArgs(object): 
    def __init__(self, *deco_args, **deco_kwargs): 
     self.deco_args = deco_args 
     self.deco_kwargs = deco_kwargs 
    def __call__(self, func): 
     def _wrap(self, *args, **kwargs): 
      print "Blah blah blah" 
      return func(*args, **kwargs) 
     return _wrap 

suo nulla se non un po 'più chiara

4

I sai che hai detto che sembra non ottimale, ma sento ancora che l'utilizzo di tre modelli annidati è la soluzione più pulita. Le due funzioni interne sono solo il modo "normale" di definire un decoratore per una funzione che accetta argomenti (vedere example nei documenti di python per @wraps). Quello esterno è in realtà solo una funzione che accetta e argomenta e restituisce un decoratore.

def with_timeout(timeout): 
    def decorator(f): 
     @wraps(f) 
     def wrapper(*args, **kwargs): 
      with Timeout(timeout): 
       return f(*args, **kwargs) 
     return wrapper 
    return decorator 
+0

Siamo spiacenti, ma non sono d'accordo. Non c'è nulla di pulito riguardo a tre funzioni annidate (o, peraltro, al modo in cui lo faccio). È un hack. Sarebbe meglio, ad esempio, un decoratore di '@ decorator_with_arguments' (vedi la mia domanda aggiornata). –

4

Basato sul suggerimento di Jakob, ho implementato un piccolo Decorator classe, che mi sento fa un lavoro abbastanza decente:

class Decorator(object): 
    def __call__(self, f): 
     self.f = f 
     return functools.wraps(f)(lambda *a, **kw: self.wrap(*a, **kw)) 

    def wrap(self, *args, **kwrags): 
     raise NotImplemented("Subclasses of Decorator must implement 'wrap'") 

class with_timeout(Decorator): 
    def __init__(self, timeout): 
     self.timeout = timeout 

    def wrap(self, *args, **kwargs): 
     with Timeout(timeout): 
      return self.f(*args, **kwargs) 
+1

Ho corretto un errore di battitura nel tuo metodo 'with_timeout'' warp' –

+0

D'oh! Grazie. Questa è probabilmente l'ottava volta che ho digitato "warp" invece di "wrap" stasera. E non ho digitato 'wrap' così tante volte. –

0

In primo luogo, si può definire un po 'di meta-decorator:

def decorator_with_arguments(wrapper): 
    return lambda *args, **kwargs: lambda func: wrapper(func, *args, **kwargs) 

che ci permette di creare decoratori che accettano argomenti in questo modo:

@decorator_with_arguments 
def my_wrapper(func, *decorator_args, **decorator_kwargs): 
    def wrapped(*call_args, **call_kwargs): 
     print "from decorator:", decorator_args, decorator_kwargs 
     func(*call_args, **call_kwargs) 
    return wrapped 

che può quindi essere utilizzato normalmente:

@my_wrapper(1, 2, 3) 
def test(*args, **kwargs): 
    print "passed directly:", args, kwargs 

test(4, 5, 6) 

Aggiunta functools.wraps decorazione è lasciata come esercizio :)

0

Un'altra prendere, senza l'utilizzo di lambda:

def decorator_with_arguments(f): 
    @functools.wraps(f) 
    def with_arguments_helper(*args, **kwargs): 
     def decorator(g): 
      return f(g, *args, **kwargs) 
     return decorator 
    return with_arguments_helper 
Problemi correlati