Pythonic方法以适当的单位返回值

Mic*_*ter 10 python design-patterns units-of-measurement python-2.7

目标:从调用者请求的单位(或任何微不足道的修改)中的函数返回一个值.

背景:

我在Raspberry Pi 3上运行Python 2.7,并使用该函数distance()来获取旋转编码器已转动的距离.我需要不同单位的距离,具体取决于调用函数的位置.那怎么说,这应该是pythonically(即简短,易于维护).

第一次尝试:

我的第一次尝试是在函数中使用一个单位,并有一个长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
Run Code Online (Sandbox Code Playgroud)

这种方法的好处在于它有一种识别不可用单元的方法.

第二次尝试:

我的第二次尝试是创建一个包含各种字典的字典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
Run Code Online (Sandbox Code Playgroud)

在这里,未被识别的单位被捕获KeyError.

关联:

我知道像Pint这样的现有库,但我正在寻找这个编程问题的解决方案.当你在Python中有一个函数时,你需要以可重用的方式对输出稍作修改.我有其他功能,例如speed()使用'm/s'作为基本单元,并且需要类似的units参数.根据我的经验,结构良好的程序elif在每个返回语句之前不涉及一段分支.在这种情况下,如果我想改变计算单位的方式,我必须仔细grep检查我的代码,并确保在每个实例中更改单位的计算方式.一个适当的解决方案只需要改变计算一次.

这可能过于宽泛,但这是我一直遇到的模式.

JL *_*ret 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")
Run Code Online (Sandbox Code Playgroud)

输出:

10.0
10.94
-10
Run Code Online (Sandbox Code Playgroud)

或者,作为一个更通用的包装器,它可以解决任何无单元功能:

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)
Run Code Online (Sandbox Code Playgroud)

输出:

 10.0
 21.88
 -30
Run Code Online (Sandbox Code Playgroud)

关于提高KeyError vs -1的一点是味道问题.就个人而言,如果没有找到,我会返回*1(如果接收者不在乎).或抛出一个KeyError.-1显然没用.

最后一次迭代,使unit参数可选:

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
Run Code Online (Sandbox Code Playgroud)


che*_*ner 5

字典查找是好的,但不返回sentinel值来表示错误; 只是提出一个适当的例外.它可以像让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
Run Code Online (Sandbox Code Playgroud)