2016-06-29 1 views
10

Objectif: retourner une valeur d'une fonction dans les unités (ou toute modification triviale) demandée par l'appelant.Façon pythonienne d'avoir une valeur de retour de fonction dans les unités appropriées

Contexte:

Je courais Python 2.7 sur un Raspberry Pi 3, et utiliser la fonction distance() pour obtenir la distance un codeur rotatif a tourné. J'ai besoin de cette distance dans différentes unités en fonction de l'endroit où la fonction est appelée. Comment alors, devrait-il être écrit pythoniquement (c'est-à-dire court et facile à maintenir).

première tentative.

Ma première tentative a été d'utiliser une unité de mètres dans la fonction, et ont un long arbre elif pour sélectionner les unités droite pour retourner dans

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 bonne chose à propos de cette approche est qu'elle a un moyen de reconnaître une unité qui n'est pas disponible.

Deuxième tentative:

Ma deuxième tentative a été de créer un dictionnaire pour contenir divers 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 

Ici, les unités non reconnues sont pris avec un KeyError.

Pertinence:

Je sais que des bibliothèques existantes comme Pint, mais je cherche une solution à ce problème de programmation. Lorsque vous avez une fonction en Python, et que vous avez besoin d'apporter de légères modifications à la sortie de manière réutilisable. J'ai d'autres fonctions telles que speed() qui utilisent 'm/s' comme unité de base, et ont besoin d'un argument units similaire. D'après mon expérience, un programme bien structuré n'implique pas un paragraphe de elif branches avant chaque déclaration de retour. Dans ce cas, si je voulais changer la manière dont je calcule les unités, je devrais faire attention à grep à travers mon code, et m'assurer de changer la façon dont les unités sont calculées à chaque instance. Une solution adéquate ne nécessiterait qu'une modification du calcul une fois.

Ceci est peut-être trop large, mais c'est un modèle que je continue à courir.

+1

Personnellement, j'aller avec la deuxième tentative, mais si je voulais l'utiliser beaucoup C# dev intérieur de moi dirait faire une méthode d'extension ' .convertTo ('ms') 'par exemple. Ensuite, vous pouvez faire 'encoderReading.convertTo ('m')' et je pense que la syntaxe est vraiment belle. –

+0

Donc, vous savez que toutes vos valeurs sont des puissances de 10 et vous savez aussi que les entiers sont plus rapides que les chaînes, donc vous pouvez l'optimiser en mappant les valeurs statiques du multiplicateur en celles définies dynamiquement en utilisant map et lambda. – dmitryro

+0

Malheureusement, ma mise en œuvre réelle a des unités non SI (c'est-à-dire des yards, des miles) également. Tout le monde veut que les données soient affichées dans leur unité de choix. Je vais mettre à jour ma question pour refléter cela. –

Répondre

6

Que diriez-vous, en utilisant un décorateur:

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") 

sortie:

10.0 
10.94 
-10 

ou, comme un emballage plus générique qui contourne une unité moins fonction:

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) 

sortie:

10.0 
21.88 
-30 

Le bit sur lever une KeyError vs -1 est une question de goût. Personnellement, je retournerais * 1 si non trouvé (si le récepteur s'en fichait). Ou jeter une KeyError. Le -1 n'est pas évidemment utile.

itération dernière, faisant le paramètre unité en option:

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 recherche de dictionnaire est bonne, mais ne renvoie pas de valeur sentinelle pour signaler une erreur; il suffit de lever une exception appropriée. Il pourrait être aussi simple (mais opaque) que de laisser se propager le KeyError dans votre recherche. Une meilleure solution, cependant, est de soulever une exception personnalisée:

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

Alors que c'est bien dans les réponses simples, brèves, je tiens à ajouter que généralement, lors de la définition d'une classe vide comme celle-ci, vous ne devriez pas utiliser pass, mais utiliser une chaîne doc expliquant la classe à la place. – Keozon

4

Pour votre exemple, il pourrait être:

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 

Mais, il peut être raisonnable de se déplacer multipliers en dehors de distance fonction et faire partie de DistanceUnits.

UPD: il y a beaucoup de façons différentes de « comment .. » et tous dépend de vos besoins, mais il y a un principe de base SEC. Même beaucoup de elif s peuvent être assez bon (la création d'instance de dictionnaire consomme un peu de ram sur chaque appel de fonction ..) si vous n'oublier pas ne vous répétez pas.