2016-06-29 35 views
10

Obiettivo: restituire un valore da una funzione nelle unità (o qualsiasi piccola modifica) richiesta dal chiamante.Modo Pythonic per avere un valore di ritorno di una funzione in unità corrette

Priorità:

Sono in esecuzione Python 2.7 da un lampone Pi 3, e utilizzare la funzione distance() per ottenere la distanza un encoder rotativo ha trasformato. Ho bisogno di questa distanza in unità diverse a seconda di dove viene chiamata la funzione. Come allora, dovrebbe essere scritto pitonicamente (cioè breve, e facilmente mantenuto).

First Attempt:.

Il mio primo tentativo è stato quello di utilizzare un'unità di metri nella funzione, e hanno una lunga elif albero per selezionare le unità giuste per tornare in

def distance(units='m'): 
    my_distance = read_encoder() 

    if units == 'm': 
     return my_distance * 1.000 
    elif units == 'km': 
     return my_distance/1000. 
    elif units == 'cm': 
     return my_distance * 10.00 
    elif units == 'yd': 
     return my_distance * 1.094 
    else: 
     return -1 

La cosa bella di questo approccio è che ha un modo per riconoscere un'unità che non è disponibile.

secondo tentativo:

Il mio secondo tentativo è stato quello di creare un dizionario per contenere vari multipliers.

def distance(units='m'): 
    multiplier = { 
     'm': 1.000, 
     'km': 0.001, 
     'cm': 10.00 
     'yd': 1.094 
    } 

    try: 
     return read_encoder() * mulitplier[units] 
    except KeyError: 
     return -1 

Qui, le unità non riconosciute sono catturati con un KeyError.

Rilevanza:

So di librerie esistenti come Pint, ma sono alla ricerca di una soluzione a questo problema di programmazione. Quando hai una funzione in Python, e devi apportare lievi modifiche all'output in modo riutilizzabile. Ho altre funzioni come speed() che usano 'm/s' come unità di base e ho bisogno di un argomento simile units. Dalla mia esperienza, un programma ben strutturato non coinvolge un paragrafo di rami elif prima di ogni dichiarazione di ritorno. In questo caso, se volessi cambiare il modo in cui calcolerò le unità, dovrei fare attenzione a grep attraverso il mio codice e assicurarmi di cambiare il modo in cui le unità vengono calcolate ad ogni istanza. Una soluzione adeguata richiederebbe solo una modifica del calcolo una volta.

Questo è probabilmente troppo ampio, ma è un modello su cui continuo a correre.

+1

Personalmente vorrei andare con il secondo tentativo, anche se avevo intenzione di usare un sacco di C# dev dentro di me direbbe rendere un metodo di estensione ' .convertTo ('ms') 'per esempio. Quindi potresti eseguire 'encoderReading.convertTo ('m')' e penso che la sintassi sia davvero bella. –

+0

Quindi sai che tutti i tuoi valori sono potenze di 10 e sai anche che gli interi sono più veloci delle stringhe, quindi puoi ottimizzarlo ulteriormente mappando i valori statici del moltiplicatore in quelli impostati dinamicamente usando map e lambda. – dmitryro

+0

Sfortunatamente, la mia implementazione effettiva ha anche unità non SI (cioè yard, miglia). Tutti vogliono i dati visualizzati nella loro unità di scelta. Aggiornerò la mia domanda per riflettere questo. –

risposta

6

ne dite, utilizzando un decoratore:

def read_encoder(): 
    return 10 

multiplier = { 
    'm': 1.000, 
    'km': 0.001, 
    'cm': 10.00, 
    'yd': 1.094, 
} 

def multiply(fn): 
    def deco(units): 
     return multiplier.get(units, -1) * fn(units) 
    return deco 

@multiply 
def distance(units='m'): 
    my_distance = read_encoder() 
    return my_distance 

print distance("m") 
print distance("yd") 
print distance("feet") 

uscita:

10.0 
10.94 
-10 

o, come un involucro più generico che va in giro qualsiasi unità-less funzione:

multiplier = { 
    'm': 1.000, 
    'km': 0.001, 
    'cm': 10.00, 
    'yd': 1.094, 
} 

def multiply(fn): 
    def deco(units, *args, **kwds): 
     return multiplier.get(units, -1) * fn(*args, **kwds) 
    return deco 


@multiply 
def read_encoder(var): 
    #I've added a variable to the function just to show that 
    #it can be passed through from the decorator 
    return 10 * var 

print read_encoder("m", 1) 
print read_encoder("yd", 2) 
print read_encoder("feet", 3) 

output:

10.0 
21.88 
-30 

Il bit su raise a KeyError contro -1 è una questione di gusto. Personalmente, mi piacerebbe restituire * 1 se non trovato (se il ricevitore non importava). O lanciare un KeyError. Il -1 non è ovviamente utile.

ultima iterazione, rendendo il parametro unità opzionale:

def multiply(fn): 
    def deco(*args, **kwds): 
     #pick up the unit, if given 
     #but don't pass it on to read_encoder 
     units = kwds.pop("units", "m") 

     return multiplier.get(units, -1) * fn(*args, **kwds) 
    return deco 


@multiply 
def read_encoder(var): 
    return 10 * var 

print read_encoder(1, units="yd") 
print read_encoder(2) 
print read_encoder(3, units="feet") 


10.94 
20.0 
-30 
5

La ricerca del dizionario è buona, ma non restituire un valore sentinella per segnalare un errore; basta sollevare un'eccezione appropriata. Potrebbe essere semplice (anche se opaco) come lasciare propagare il numero KeyError nella tua ricerca. Una soluzione migliore, però, è quello di sollevare un'eccezione personalizzato:

class UnknownUnitError(ValueError): 
    pass 

def distance(unit='m'): 
    multiplier = { 
     'm': 1.000, 
     'km': 0.001, 
     'cm': 10.00 
    } 

    try: 
     unit = multiplier[unit] 
    except KeyError: 
     # Include the problematic unit in the exception 
     raise UnknownUnitError(unit) 

    return read_encoder() * unit 
+1

Mentre sta bene in risposte semplici e brevi, voglio aggiungere che generalmente, quando si definisce una classe vuota come questa, non si dovrebbe usare il pass, ma usare una doc cord che spieghi invece la classe. – Keozon

4

Per esempio potrebbe essere:

class DistanceUnits(): 
    """ 
    Enum class for indicating measurement units and conversions between them. 
    Holds all related logic. 
    """ 
    M = 'm' 
    KM = 'km' 
    CM = 'cm' 


def distance(units=DistanceUnits.M): 
    multiplier = { 
     DistanceUnits.M: 1.000, 
     DistanceUnits.KM: 0.001, 
     DistanceUnits.CM: 10.00 
    } 
    return read_encoder() * mulitplier[units] if units in multiplier else -1 

Ma, può essere ragionevole per spostare multipliers al di fuori di distance funzioni e renderlo parte di DistanceUnits.

UPD: ci sono un sacco di modi diversi di 'come ..' e tutti loro dipende da voi ha bisogno, ma c'è un principio fondamentale DRY. Anche un sacco di elif s può essere abbastanza buono (la creazione di un'istanza del dizionario consuma qualche ram su ogni chiamata di funzione ..) se non si dimentica di non ripetersi.