2015-09-16 14 views
5

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:

  1. 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)
  2. 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
  3. .210
  4. 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): 
     .... 

risposta

3
  1. Sono d'accordo con te, che si tratta di una soluzione migliore, quindi niente da fare qui.
  2. Penso che otterrai un buon ordine e lo terrò ulteriormente, il che non diventerà un problema in futuro.
  3. Questo è un problema, hai ragione, ma è stato risolto facilmente con functools.wraps. Basta decorare il tuo newfunc con esso e si salverà la firma della funzione originale.

    from functools import wraps 
    
    def convertparameters(*types): 
        def wrapper(func): 
         @wraps(func) 
         def new_func(self, *args, **kwargs): 
          pass # Your stuff 
    

    duro, questo funziona solo per Python 3. In Python 2, la firma non sarebbe conservata, solo __name__ e __doc__ sarebbe. Quindi, in caso di Python, è possibile utilizzare il modulo decorator:

    from decorator import decorator 
    
    def convertparameters(*types): 
        @decorator 
        def wrapper(func, self, *args, **kwargs): 
         pass # return a result 
        return wrapper 
    

EDIT sulla base di user3160867's aggiornamento.

+0

Grazie, non sapevo di 'wraps'. Presumo che ci dovrebbero essere anche 'return new_func' e' return wrapper'? – Vladimir

+0

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

+0

Immagino che tu stia usando Python 2. Nel caso di Python 3 la firma verrebbe anche mantenuta. Controlla il mio aggiornamento. – bagrat

1

Per rendere il codice un po 'più conciso, non fornire argomenti al decoratore - dedurli dalla funzione decorata.

+0

Sembra interessante! Intendi dichiarare 'def wrapper (func, self, ** kwargs)' senza 'args' e deducendo dal' kwargs'? – Vladimir

+0

No, puoi usare questo: http://stackoverflow.com/questions/218616/getting-method-parameter-names-in-python – zmbq