Sto scrivendo una classe che ha un certo numero di metodi che operano su simili tipi di argomenti:modo Pythonic di convertire parametri per lo stesso standard in tutti i metodi di una classe
class TheClass():
def func1(self, data, params, interval):
....
def func2(self, data, params):
....
def func3(self, data, interval):
....
def func4(self, params):
....
...
C'è una certa convenzione su questi argomenti (ad esempio data
/params
devono essere numpy.farrays
, interval
- a list
di 2 floats
), ma vorrei consentire all'utente di avere più libertà: ad es. funzioni devono accettare int
come data
o params
, o uno solo int
come interval
quindi supporre che questo è il punto finale con il punto di partenza 0
, ecc
Quindi, per evitare tutte queste trasformazioni all'interno dei metodi, che dovrebbe fare solo la logica, sto usando decoratori come questo:
def convertparameters(*types):
def wrapper(func):
def new_func(self, *args, **kwargs):
# Check if we got enough parameters
if len(types) > len(args):
raise Exception('Not enough parameters')
# Convert parameters
new_args = list(args)
for ind, tip in enumerate(types):
if tip == "data":
new_args[ind] = _convert_data(new_args[ind])
elif tip == "params":
new_args[ind] = _convert_params(new_args[ind])
elif tip == "interval":
new_args[ind] = _convert_interval(new_args[ind])
else:
raise Exception('Unknown type for parameter')
return func(self, *new_args, **kwargs)
return new_func
return wrapper
Dove _convert_data
, _convert_params
e _convert_interval
fare il lavoro sporco. Poi mi definisco la classe come segue:
class TheClass():
@convertparameters("data", "params", "interval")
def func1(self, data, params, interval):
....
@convertparameters("data", "params")
def func2(self, data, params):
....
@convertparameters("data", "interval")
def func3(self, data, interval):
....
@convertparameters("params")
def func4(self, params):
....
...
Si fa il trucco, ma ci sei diverse cose abbastanza inquietanti:
- E ingombra il codice (anche se è un problema minore, e trovo questa soluzione per essere molto più compatta della chiamata esplicita della funzione di trasformazione all'inizio di ogni metodo)
- Se ho bisogno di combinare questo decoratore con un altro decoratore (
@staticmethod
o qualcosa che l'output post-elaborazione del metodo) l'ordinamento di questi decoratori contano .210
- La cosa più inquietante è che il decoratore di questa forma nasconde completamente la struttura dei parametri del metodo e IPython mostra come
func1(*args, **kwargs)
Ci sono dei migliori (o più "divinatorio") modi per fare una così grande trasformazione dei parametri?
UPDATE 1: SOLUZIONE basato su n9code s' suggerimento
Per evitare confusione c'è una modifica della convertparameters
involucro che risolve 3a edizione (mascheratura di firma e docstring dei metodi) - suggerito da n9code per Python> 2.5.
Utilizzando il modulo decorator
(per essere installato separatamente: pip install decorator
) siamo in grado di trasferire i "metadati" (docstring, nome e la firma), tutti della funzione allo stesso tempo sbarazzarsi di struttura annidata all'interno della confezione
from decorator import decorator
def convertparameters(*types):
@decorator
def wrapper(func, self, *args, **kwargs):
# Check if we got enough parameters
if len(types) > len(args):
raise Exception('Not enough parameters')
# Convert parameters
new_args = list(args)
for ind, tip in enumerate(types):
if tip == "data":
new_args[ind] = _convert_data(new_args[ind])
elif tip == "params":
new_args[ind] = _convert_params(new_args[ind])
elif tip == "interval":
new_args[ind] = _convert_interval(new_args[ind])
else:
raise Exception('Unknown type for parameter')
return func(self, *new_args, **kwargs)
return wrapper
UPDATE 2: MODIFICATO SOLUZIONE basa su zmbq 's suggerimento
Usando inspect
modulo potremmo anche sbarazzarsi di argomenti di decoratore, controllando i nomi di Argu menti della funzione iniziale.Questo eliminerà un altro livello del decoratore
from decorator import decorator
import inspect
@decorator
def wrapper(func, self, *args, **kwargs):
specs = inspect.getargspec(func)
# Convert parameters
new_args = list(args)
for ind, name in enumerate(specs.args[1:]):
if name == "data":
new_args[ind] = _convert_data(new_args[ind])
elif name == "params":
new_args[ind] = _convert_params(new_args[ind])
elif name == "interval":
new_args[ind] = _convert_interval(new_args[ind])
return func(self, *new_args, **kwargs)
E l'utilizzo è molto più semplice di allora. L'unica cosa importante è continuare a usare gli stessi nomi per gli argomenti tra diverse funzioni.
class TheClass():
@convertparameters
def func1(self, data, params, interval):
....
@convertparameters
def func2(self, data, params):
....
Grazie, non sapevo di 'wraps'. Presumo che ci dovrebbero essere anche 'return new_func' e' return wrapper'? – Vladimir
Ho ancora una domanda: 'wraps' trasferisce correttamente la docstring. Tuttavia la firma rimane 'func1 (* args, ** kwargs)'. C'è un modo per trasferire anche la firma? – Vladimir
Immagino che tu stia usando Python 2. Nel caso di Python 3 la firma verrebbe anche mantenuta. Controlla il mio aggiornamento. – bagrat