2013-09-04 9 views
6

Molto strettamente correlato a: How can I programmatically change the argspec of a function in a python decorator?Come posso modificare a livello di codice l'argomento di una funzione * not * in un decoratore python?

Il modulo decoratore fornisce i mezzi per eseguire una funzione di decorazione che conserva l'argspec della funzione decorata.

Se si definisce una funzione che non viene utilizzata come decoratore, esiste un modo per copiare l'argomento di un'altra funzione?

Esempio caso d'uso:

class Blah(object): 
    def foo(self, *args, **kwargs): 
     """ a docstr """ 
     result = bar(*args, **kwargs) 
     result = result**2 # just so it's clear we're doing something extra here... 
     return result 

def bar(x, y, z=1, q=2): 
    """ a more useful docstr, saying what x,y,z,q do """ 
    return x+y*z+q 

vorrei avere foo 's argspec assomigliare bar' s, ma la fonte di rimanere invariato (vale a dire, inspect.getsource(foo) sarebbe ancora mostrare la result spazzatura). Lo scopo principale di questo è quello di ottenere i documenti sfinge e la guida interattiva di ipython per mostrare gli argomenti appropriati.

Come le risposte all'altra domanda, il decorator package mostra un modo per farlo, ma mi sono perso all'interno della carne di quel codice. Sembra che il pacchetto decorator stia ricompilando il codice sorgente, o qualcosa del genere. Avevo sperato in un approccio più semplice, ad es. qualcosa come foo.argspec = bar.argspec, sarebbe possibile.

+0

Sì che qualche fonte peloso codice in quella funzione decoratore. Hai ragione, sta ricostruendo la funzione - suggerirei di provare a riutilizzare il metodo di classe FunctionMaker.create e vedere come vai. – Hamish

+0

Pensandoci però, sembra che ci vorrà più lavoro, piuttosto che migliorare la firma originale o aggiungere un docblock appropriato. – Hamish

+0

'pippo' è in realtà un metodo, quindi il primo argomento sarà _always_ essere l'oggetto istanziato, a meno che non si desideri che' foo' sia un 'staticmethod'. Altrimenti, sarebbe fuorviante controllare l'aiuto su 'Blah.foo' e non vedere' self' come primo argomento nella firma. Per non dire che non è fattibile, solo che potrebbe essere necessario ricordare di anteporre la firma 'bar' a' self'. –

risposta

4

Un decoratore è semplicemente una funzione che fa qualcosa con un'altra funzione. Quindi, tecnicamente, potresti inserire il codice richiesto direttamente sotto il metodo foo e, tecnicamente, cambieresti foo senza usare un decoratore, ma sarebbe un pasticcio orribile.

Il modo più semplice per fare ciò che si vuole è fare un decoratore che prende una seconda funzione (bar in questo caso) come argomento in modo da sapere quale firma copiare. Il codice di classe sarebbe quindi cercare qualcosa di simile:

class Blah(object): 
    @copy_argspec(bar) 
    def foo(self, *args, **kwargs): 
     """ a docstr """ 
     result = bar(*args, **kwargs) 
     result = result**2 # just so it's clear we're doing something extra here... 
     return result 

Dovrete avere bar definito prima anziché dopo la classe.

.
.
.
. . . il tempo passa. . . .
.
.

Ok, per fortuna ho trovato un vecchio decoratore che potevo adattare.

help(Blah.foo) assomiglia a questo prima della decorazione:

Help on method foo in module __main__: 

foo(self, *args, **kwargs) unbound __main__.Blah method 
    a docstr 

e dopo la decorazione sembra che questo:

Help on method foo in module __main__: 

foo(self, x, y, z=1, q=2) unbound __main__.Blah method 
    a more useful docstr, saying what x,y,z,q do 

Ecco il decoratore che ho usato:

import inspect 

class copy_argspec(object): 
    """ 
    copy_argspec is a signature modifying decorator. Specifically, it copies 
    the signature from `source_func` to the wrapper, and the wrapper will call 
    the original function (which should be using *args, **kwds). The argspec, 
    docstring, and default values are copied from src_func, and __module__ and 
    __dict__ from tgt_func. 
    """ 
    def __init__(self, src_func): 
     self.argspec = inspect.getargspec(src_func) 
     self.src_doc = src_func.__doc__ 
     self.src_defaults = src_func.func_defaults 

    def __call__(self, tgt_func): 
     tgt_argspec = inspect.getargspec(tgt_func) 
     need_self = False 
     if tgt_argspec[0][0] == 'self': 
      need_self = True 

     name = tgt_func.__name__ 
     argspec = self.argspec 
     if argspec[0][0] == 'self': 
      need_self = False 
     if need_self: 
      newargspec = (['self'] + argspec[0],) + argspec[1:] 
     else: 
      newargspec = argspec 
     signature = inspect.formatargspec(
       formatvalue=lambda val: "", 
       *newargspec 
       )[1:-1] 
     new_func = (
       'def _wrapper_(%(signature)s):\n' 
       ' return %(tgt_func)s(%(signature)s)' % 
       {'signature':signature, 'tgt_func':'tgt_func'} 
        ) 
     evaldict = {'tgt_func' : tgt_func} 
     exec new_func in evaldict 
     wrapped = evaldict['_wrapper_'] 
     wrapped.__name__ = name 
     wrapped.__doc__ = self.src_doc 
     wrapped.func_defaults = self.src_defaults 
     wrapped.__module__ = tgt_func.__module__ 
     wrapped.__dict__ = tgt_func.__dict__ 
     return wrapped 
+0

Quindi stai suggerendo che 'copy_argspec' sarebbe molto simile al decoratore , ma userebbe 'bar' come origine dell'argspec invece di' foo'? Mi piace l'idea e proverò ... – keflavich

+1

@keflavich: Se rimani bloccato, ho aggiunto il decoratore. :) –

Problemi correlati