2016-06-29 6 views
10

Цель: вернуть значение из функции в единицах (или любых тривиальных модификациях), запрошенных вызывающим абонентом.Pythonic способ получить возвращаемое значение функции в соответствующих единицах

фон:

Я бег на Python 2.7 на Raspberry Pi 3, а также использовать функцию distance(), чтобы получить расстояние ротационного энкодер превратил. Мне нужно это расстояние в разных единицах в зависимости от того, где вызывается функция. Как же тогда это должно быть написано пифонически (т. Е. Коротко и легко поддерживается).

Первая попытка:.

Моя первая попытка была использовать единицу метров в функции, и имеют длинный elif дерево, чтобы выбрать правильные единицы, чтобы вернуться в

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 

Приятная вещь в этом подходе заключается в том, что у него есть способ распознать устройство, которое недоступно.

Вторая попытка:

Моя вторая попытка была создать словарь, чтобы содержать различные 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 

Здесь непризнанные единицы пойманы с KeyError.

релевантности:

Я знаю существующих библиотек, как Pint, но я ищу решение этой проблемы программирования. Когда у вас есть функция в Python, и вам нужно внести небольшие изменения в результат многоразовым способом. У меня есть другие функции, такие как speed(), которые используют «м/с» в качестве базового блока и нуждаются в аналогичном аргументе units. По моему опыту, хорошо структурированная программа не включает параграф elif ветвей перед каждым оператором возврата. В этом случае, если бы я хотел изменить способ вычисления единиц, мне пришлось бы тщательно выполнить grep через мой код и убедиться, что я изменяю способ вычисления единиц в каждом экземпляре. Правильное решение потребовало бы только однократного изменения расчета.

Возможно, это слишком широкий, но это шаблон, в котором я постоянно сталкиваюсь.

+1

Лично я бы пойти со второй попытки, хотя, если я намеревался использовать его много С # DEV внутри меня сказал бы сделать метод расширения ' .convertTo ('ms') 'например. Тогда вы можете сделать 'encoderReading.convertTo ('m')', и я думаю, что синтаксис выглядит очень хорошо. –

+0

Итак, вы знаете, что все ваши значения имеют значение 10, и вы также знаете, что целые числа быстрее, чем строки, поэтому вы можете дополнительно оптимизировать его, сопоставляя статические значения множителя в динамически заданные с помощью карты и лямбда. – dmitryro

+0

К сожалению, моя фактическая реализация также имеет единицы не SI (например, ярды, мили). Все хотят, чтобы данные отображались в их выборке. Я обновлю свой вопрос, чтобы это отразить. –

ответ

6

Как насчет использования декоратора:

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

выход:

10.0 
10.94 
-10 

или, как более общий обертку, которая идет вокруг какой-либо блок-менее функции:

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) 

выход:

10.0 
21.88 
-30 

Немного о повышении KeyError против -1 - это вопрос вкуса. Лично я бы вернул * 1, если не нашел (если получателю все равно). Или бросьте KeyError. Значение -1 явно не полезно.

Последняя итерация, что делает параметр блока дополнительно:

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

Словарь поиска хорош, но не возвращает значение часового, чтобы сигнализировать об ошибке; просто поднимите соответствующее исключение. Это может быть так же просто (хотя и непрозрачно), что позволяет KeyError в вашем поиске распространяться. Лучшее решение, однако, является то, чтобы поднять пользовательское исключение:

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

В то время как его тонкие простые, короткие ответы, я хочу добавить, что в общем случае при определении пустого класса, подобного этому, вы не должны использовать pass, но вместо этого используйте строку doc, объясняющую класс. – Keozon

4

Для примера это может быть:

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 

Но, это может быть разумным, чтобы переместить multipliers вне distance функции и сделать его часть DistanceUnits.

UPD: Есть много различных способов «как ..» и все из них зависит от вас нужно, но есть один главный принцип DRY. Даже много elif s могут быть достаточно хорошими (создание словаря, использующего некоторый барабан при каждом вызове функции ..), если вы не забудете, не повторяйте себя.