2009-09-11 26 views
17

Supponiamo di avere una funzione generica f. Voglio programmaticamente creare una funzione f2 che si comporta come f, ma ha una firma personalizzata.Imposta la firma della funzione in Python

Maggiori dettagli

Dato un elenco l ed e dizionario d voglio essere in grado di:

  • impostare gli argomenti non di parole chiave di f2 alle corde in l
  • Set gli argomenti della parola chiave di f2 alle chiavi in ​​d e i valori predefiniti ai valori di d

ie. Supponiamo di avere

l=["x", "y"] 
d={"opt":None} 

def f(*args, **kwargs): 
    #My code 

Poi vorrei una funzione con la firma:

def f2(x, y, opt=None): 
    #My code 

un uso specifico caso

Questa è solo una versione semplificata del mio specifico caso d'uso. Sto dando questo solo come esempio.

Il mio caso di utilizzo effettivo (semplificato) è il seguente. Abbiamo una funzione generica di avvio:

def generic_init(self,*args,**kwargs): 
    """Function to initiate a generic object""" 
    for name, arg in zip(self.__init_args__,args): 
     setattr(self, name, arg) 
    for name, default in self.__init_kw_args__.items(): 
     if name in kwargs: 
      setattr(self, name, kwargs[name]) 
     else: 
      setattr(self, name, default) 

Vogliamo utilizzare questa funzione in un numero di classi. In particolare, vogliamo creare una funzione init che si comporta come generic_init, ma ha la firma definito da alcune variabili di classe al momento della creazione:

class my_class: 
    __init_args__=["x", "y"] 
    __kw_init_args__={"my_opt": None} 

__init__=create_initiation_function(my_class, generic_init) 
setattr(myclass, "__init__", __init__) 

Vogliamo create_initiation_function per creare una nuova funzione con la firma definito utilizzando init_args e kw_init_args. È possibile scrivere create_initiation_function?

Si prega di notare:

  • Se Volevo solo migliorare l'aiuto, ho potuto impostare doc.
  • Vogliamo impostare la firma della funzione sulla creazione. Dopodiché, non ha bisogno di essere cambiato.
  • Invece di creare una funzione come generic_init, ma con una firma diversa, potremmo creare una nuova funzione con la firma desiderata che chiama semplicemente generic_init
  • Vogliamo definire create_initiation_function. Non vogliamo specificare manualmente la nuova funzione!

correlati

+0

Ovviamente vuoi sapere qualcosa di completamente diverso da quello che stai chiedendo. Forse dovresti spiegare qual è il problema che stai avendo? Questo potrebbe dirci cosa stai cercando di chiedere. –

+1

Stai chiedendo come programmare una tale funzione?Ad ogni modo, penso che tu debba darci un contesto. Come vuoi usarlo? Come si desidera specificare il corpo della funzione? Puoi mostrarci come vorresti utilizzarlo? –

+0

È più chiaro ora che l'ho modificato di nuovo? – Casebash

risposta

13

Per il tuo caso, avere una docstring in classe/la funzione dovrebbe funzionare - che apparirà in help() ok, e può essere impostato a livello di codice (func .__ doc__ = "stuff").

Non riesco a vedere alcun modo per impostare la firma effettiva. Avrei pensato che lo functools module avrebbe fatto se fosse fattibile, ma non lo fa, almeno in py2.5 e py2.6.

È anche possibile generare un'eccezione TypeError in caso di input errati.

Hmm, se non ti dispiace essere veramente vili, puoi usare compile()/eval() per farlo. Se la firma desiderata è specificato da arglist = [ "foo", "bar", "baz"], e la funzione reale è f (* args, ** kwargs), è possibile gestire:

argstr = ", ".join(arglist) 
fakefunc = "def func(%s):\n return real_func(%s)\n" % (argstr, argstr) 
fakefunc_code = compile(fakefunc, "fakesource", "exec") 
fakeglobals = {} 
eval(fakefunc_code, {"real_func": f}, fakeglobals) 
f_with_good_sig = fakeglobals["func"] 

help(f)    # f(*args, **kwargs) 
help(f_with_good_sig) # func(foo, bar, baz) 

Cambiare docstring e func_name dovrebbero ottenere una soluzione completa. Ma, uh, eww ...

+2

Avrei dovuto pensare a eval. Tutto è possibile con esso. – Casebash

+7

Questo è il pattern usato dal pacchetto decoratore - http://pypi.python.org/pypi/decorator/ –

+1

completamente vile? questo è esattamente ciò che fa "namedtuple". –

-4

Edit 1: Rispondere nuova domanda:

Mi chiedi come è possibile creare una funzione con questa firma:

def fun(a, b, opt=None): 
    pass 

Il corretto modo di fare che in Python è quindi:

def fun(a, b, opt=None): 
    pass 

Edit 2: spiegazione telefonica:

"Supponiamo di avere una funzione generica f. Voglio creare a livello di programmazione una funzione f2 che si comporta come f, ma ha una firma personalizzata "

def f(*args, **kw): 
    pass 

OK, quindi f2 assomiglia così:.

def f2(a, b, opt=None): 
    f(a, b, opt=opt) 

Anche in questo caso, la risposta alla tua la domanda è così banale che ovviamente vuoi sapere qualcosa di diverso da quello che stai chiedendo. Devi davvero smettere di fare domande astratte e spiegare il tuo problema concreto.

+2

Non risponde alla domanda. Scusa per non essere chiaro, l'ho chiarito ora. – Casebash

+0

Ha risposto alla domanda. Ho anche aggiornato la risposta per riflettere i tuoi cambiamenti. Risponde ancora alla domanda. Probabilmente non ti dico cosa vuoi sapere, ma devi dirci cosa vuoi sapere prima. –

+0

Ero abbastanza sicuro che il significato fosse chiaro dopo averlo modificato, ma l'ho modificato di nuovo. – Casebash

0

Non è possibile farlo con codice live.

Cioè, ti sembra di essere vogliono prendere una vera funzione, dal vivo che assomiglia a questo:

def f(*args, **kwargs): 
    print args[0] 

e modificarlo in uno come questo:

def f(a): 
    print a 

La ragione per questo può Si può fare - almeno senza modificare il vero bytecode Python - è perché questi si compilano in modo diverso.

Il primo genera una funzione che riceve due parametri: un elenco e un dict e il codice che si sta scrivendo opera in tale elenco e imposta. Il secondo risulta in una funzione che riceve un parametro e a cui si accede direttamente come variabile locale. Se è stata modificata la funzione di "firma", per così dire, che sarebbe risultato in una funzione come questa:

def f(a): 
    print a[0] 

che ovviamente non avrebbe funzionato.

Se si desidera più dettagli (anche se non è di grande aiuto), una funzione che richiede * args o * kwargs ha uno o due bit impostati in f.func_code.co_flags; puoi esaminarlo tu stesso La funzione che accetta un parametro regolare ha f.func_code.co_argcount impostato su 1; la versione * args è 0. Questo è ciò che Python usa per capire come impostare lo stack frame della funzione quando viene chiamato, per controllare i parametri, ecc.

Se si vuole giocare con la modifica diretta della funzione-- se solo per convincerti che non funzionerà - vedi this answer per come creare un oggetto codice e una funzione live da uno esistente per modificarne i bit. (Questa roba è documentata da qualche parte, ma non riesco a trovarla, non è da nessuna parte nei tipi di documenti modulo ...)

Detto questo, è possibile modificare dinamicamente la docstring di una funzione. Basta assegnare a func.__doc__. Assicurati di farlo solo in fase di caricamento (dal contesto globale o - molto probabilmente - da un decoratore); se lo fai più tardi, gli strumenti che caricano il modulo per esaminare le docstring non lo vedranno mai.

+0

Non ho bisogno di modificare dinamicamente la firma di una funzione, solo per essere in grado di creare una funzione che fa la stessa cosa di un'altra funzione e ha una firma diversa – Casebash

+0

Questa è la ... stessa cosa. –

+0

Non proprio. Come sottolineato da Città, potremmo semplicemente creare una nuova funzione utilizzando eval che può chiamare la funzione generica – Casebash

1

Vogliamo create_initiation_function per cambiare la firma

Si prega di non farlo.

Vogliamo utilizzare questa funzione in una serie di classi

Si prega di utilizzare l'ereditarietà ordinario.

Non c'è alcun valore nell'avere la firma "modificata" in fase di esecuzione.

Stai creando un incubo di manutenzione. Nessun altro si prenderà la briga di capire cosa stai facendo. Lo strapperanno e lo sostituiranno con l'ereditarietà.

Fate invece questo. È semplice e ovvio e rende il tuo init generico disponibile in tutte le sottoclassi in modo ovvio, semplice, pitonico.

class Super(object): 
    def __init__(self, *args, **kwargs): 
     # the generic __init__ that we want every subclass to use 

class SomeSubClass(Super): 
    def __init__(self, this, that, **kwdefaults): 
     super(SomeSubClass, self).__init__(this, that, **kwdefaults) 

class AnotherSubClass(Super): 
    def __init__(self, x, y, **kwdefaults): 
     super(AnotherSubClass, self).__init__(x, y, **kwdefaults) 
0

forse non ho capito bene il problema, ma se si tratta di mantenere lo stesso comportamento mentre si cambia la firma di funzione, allora si può fare qualcosa di simile:

# define a function 
def my_func(name, age) : 
    print "I am %s and I am %s" % (name, age) 

# label the function with a backup name 
save_func = my_func 

# rewrite the function with a different signature 
def my_func(age, name) : 
    # use the backup name to use the old function and keep the old behavior 
    save_func(name, age) 

# you can use the new signature 
my_func(35, "Bob") 

Questo uscite:

I am Bob and I am 35 
+0

deve essere programmaticamente. È difficile da spiegare, ma suppongo che un'ultima modifica non mi ucciderebbe – Casebash

11

da PEP-0362, c'è in realtà sembra invece esserci un modo per impostare la firma nel py3.3 +, utilizzando l'attributo fn.__signature__:

def shared_vars(*shared_args): 
    """Decorator factory that defines shared variables that are 
     passed to every invocation of the function""" 

    def decorator(f): 
     @wraps(f) 
     def wrapper(*args, **kwargs): 
      full_args = shared_args + args 
      return f(*full_args, **kwargs) 

     # Override signature 
     sig = signature(f) 
     sig = sig.replace(parameters=tuple(sig.parameters.values())[1:]) 
     wrapper.__signature__ = sig 

     return wrapper 
    return decorator 

Poi:

>>> @shared_vars({"myvar": "myval"}) 
>>> def example(_state, a, b, c): 
>>>  return _state, a, b, c 
>>> example(1,2,3) 
({'myvar': 'myval'}, 1, 2, 3) 
>>> str(signature(example)) 
'(a, b, c)' 

Nota: la PEP non è esattamente a destra; Signature.replace ha spostato i parametri da un argomento posizionale a un argomento solo kw.

Problemi correlati