2016-06-29 10 views
10

Ziel: geben Sie einen Wert von einer Funktion in den Einheiten (oder jeder trivialen Änderung) zurück, die vom Aufrufer angefordert wurden.Pythonischer Weg, um einen Funktionsrückgabewert in geeigneten Einheiten zu haben

Hintergrund:

Ich bin mit Python 2.7 auf einem Raspberry Pi 3, und verwenden Sie die Funktion distance() den Abstand ein Drehgeber gedreht hat zu bekommen. Ich brauche diese Distanz in verschiedenen Einheiten, abhängig davon, wo die Funktion aufgerufen wird. Wie dann, sollte dies pythonisch geschrieben werden (d. H. Kurz, und leicht zu pflegen).

erster Versuch.

Mein erster Versuch, eine Einheit von Metern in der Funktion und einen langen elif Baum hat zu verwenden war, die richtigen Einheiten auszuwählen, die in zurückzukehren

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 

Das Schöne an diesem Ansatz ist, dass es eine Möglichkeit gibt, eine Einheit zu erkennen, die nicht verfügbar ist.

Zweiter Versuch:

Mein zweiter Versuch war ein Wörterbuch erstellen verschiedene multipliers enthalten.

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 

Hier werden nicht erkannte Einheiten mit einem KeyError gefangen.

Relevanz:

ich von vorhandenen Bibliotheken weiß wie Pint, aber für eine Lösung dieses Programmierproblem suchen. Wenn Sie in Python über eine Funktion verfügen, müssen Sie die Ausgabe leicht wiederverwenden. Ich habe andere Funktionen wie speed(), die 'm/s' als Basiseinheit verwenden, und benötigen ein ähnliches units Argument. Aus meiner Erfahrung enthält ein gut strukturiertes Programm keinen Absatz von elif Verzweigungen vor jeder Rückkehranweisung. Wenn ich in diesem Fall ändern wollte, wie ich die Einheiten berechne, müsste ich vorsichtig über meinen Code grep gehen und sicherstellen, dass ich die Berechnung der Einheiten in jedem Fall ändere. Eine richtige Lösung würde nur eine einmalige Änderung der Berechnung erfordern.

Dies ist möglicherweise zu breit, aber es ist ein Muster, in das ich laufend renne.

+1

Persönlich würde ich mit dem zweiten Versuch gehen, aber wenn ich es viel der C# Entwickler in ich sagen würden, zu verwenden beabsichtigt, eine Erweiterungsmethode machen ' .convertTo ('ms') 'zum Beispiel. Dann könnten Sie 'encoderReading.convertTo ('m')' tun und ich denke, dass Syntax wirklich nett aussieht. –

+0

Sie wissen also, dass alle Ihre Werte Potenzen von 10 sind und Sie wissen auch, dass Ganzzahlen schneller als Strings sind. Sie können sie also weiter optimieren, indem Sie statische Werte des Multiplikators mithilfe von map und lambda in dynamisch festgelegte Werte mappen. – dmitryro

+0

Leider hat meine tatsächliche Implementierung auch Nicht-SI-Einheiten (d. H. Yards, Meilen). Jeder möchte, dass Daten in seiner Auswahleinheit angezeigt werden. Ich werde meine Frage aktualisieren, um das zu reflektieren. –

Antwort

6

Wie wäre es, mit einem Dekorateur:

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

Ausgang:

10.0 
10.94 
-10 

oder als allgemeineres Wrapper, um jede einheitslose Funktion geht:

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) 

Ausgabe:

10.0 
21.88 
-30 

Das Bit über einen KeyError vs -1 zu erheben ist eine Frage des Geschmacks. Persönlich würde ich * 1 zurückgeben, wenn nicht gefunden (wenn es dem Empfänger egal war). Oder werfen Sie einen KeyError. Die -1 ist nicht offensichtlich nützlich.

letzte Iteration, so dass die Einheit Parameter optional:

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

Die Wörterbuchsuche ist gut, gibt jedoch keinen Sentinel-Wert zurück, um einen Fehler zu signalisieren; erhebe nur eine entsprechende Ausnahme. Es könnte so einfach sein (obwohl undurchsichtig) wie die KeyError in Ihrem Lookup propagieren lassen. Eine bessere Lösung ist jedoch, eine benutzerdefinierte Ausnahme zu erhöhen:

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

Während es in einfachen, kurzen Antworten gut ist, möchte ich hinzufügen, dass im Allgemeinen, wenn Sie eine leere Klasse wie diese definieren, sollten Sie nicht übergeben, sondern verwenden Sie eine Doc-Zeichenfolge, die stattdessen die Klasse zu erklären. – Keozon

4

Für Ihr Beispiel könnte es sein:

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 

Aber es sinnvoll sein kann multipliers außerhalb von distance Funktion und machen es sich zu bewegen Teil von DistanceUnits.

UPD: gibt es viele verschiedene Möglichkeiten ‚wie man ..‘ und alle von ihnen hängt von Ihnen braucht, aber es ist ein Hauptprinzip DRY. Sogar viele elif s können gut genug sein (Erstellen von Wörterbuch-Instanz verbrauchen einige RAM bei jedem Funktionsaufruf ..) Wenn Sie nicht vergessen, wiederholen Sie sich nicht.