2016-06-29 27 views
10

目的:从调用者请求的单位(或任何微小的修改)中的函数返回一个值。Pythonic方式以适当的单位获得函数返回值

背景:

我在树莓派3运行的Python 2.7,并使用功能distance()得到一个旋转编码器已经变成了距离。我需要不同单位的距离,这取决于调用函数的位置。那么,如何将其编写为Python(即简短,易于维护)。

第一次尝试:

我第一次尝试是使用在功能一个单元,并有很长的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()以'm/s'为基本单位,并且需要类似的units参数。根据我的经验,一个结构良好的程序在每个返回语句之前都不包括一段elif分支。在这种情况下,如果我想改变计算单位的方式,我必须通过我的代码仔细地对grep进行计算,并确保我改变了单位在每个实例中的计算方式。一个适当的解决方案只需要改变一次计算。

这可能过于宽泛,但它是我不断遇到的模式。

+1

个人而言,我会去的第二次尝试,但如果我打算用它了很多我内心的C#开发会说做一个扩展方法' .convertTo('ms')'例如。然后你可以执行'encoderReading.convertTo('m')',我认为这个语法看起来非常好。 –

+0

所以你知道你所有的值都是10的幂,而且你也知道整数比字符串快,所以你可以通过使用map和lambda映射乘法器的静态值到动态设置的值来进一步优化它。 – 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 vs -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 

但是,它可以合理地移动外distance功能multipliers,使它部分DistanceUnits

UPD:有很多不同的方式“如何”,他们都取决于你的需要,但有一个主要原则DRY。即使很多elif可以足够好(创建字典实例消耗每个函数调用一些RAM),如果你不忘记不要重复自己。

相关问题