Objective: return a value from a function in the units (or any trivial modification) requested by the caller.
Background:
I am running Python 2.7 on a Raspberry Pi 3, and use the function distance()
to get the distance a rotary encoder has turned. I need this distance in different units depending on where the function is called. How then, should this be written pythonically (i.e. short, and easily maintained).
First Attempt:
My first attempt was to use a unit of meters in the function, and have a long elif
tree to select the right units to return in.
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
The nice thing about this approach is that it has a way to recognize a unit that isn't available.
Second Attempt:
My second attempt was to create a dictionary to contain various 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
Here, unrecognized units are caught with a KeyError
.
Relevance:
I know of existing libraries like Pint, but am looking for a solution to this programming problem. When you have a function in Python, and you need to make slight modifications to the output in a reusable way. I have other functions such as speed()
that use 'm/s' as a base unit, and need a similar units
argument. From my experience, a well-structured program does not involve a paragraph of elif
branches before every return statement. In this case, if I wanted to change how I calculate the units, I would have to carefully grep
through my code, and make sure I change how the units are calculated at every instance. A proper solution would only require changing the calculation once.
This is possibly too broad, but it is a pattern I keep running into.
To return a value from a function, you must include a return statement, followed by the value to be returned, before the function's end statement. If you do not include a return statement or if you do not specify a value after the keyword return, the value returned by the function is unpredictable.
A return statement is used to end the execution of the function call and “returns” the result (value of the expression following the return keyword) to the caller. The statements after the return statements are not executed. If the return statement is without any expression, then the special value None is returned.
Python functions can return multiple variables. These variables can be stored in variables directly. A function is not required to return a variable, it can return zero, one, two or more variables. This is a unique property of Python, other programming languages such as C++ or Java do not support this by default.
The dictionary lookup is good, but don't return a sentinel value to signal an error; just raise an appropriate exception. It could be as simple (though opaque) as letting the KeyError
in your lookup propagate. A better solution, though, is to raise a custom exception:
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
How about, using a decorator:
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")
output:
10.0
10.94
-10
or, as a more generic wrapper that goes around any unit-less function:
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)
output:
10.0
21.88
-30
The bit about raise a KeyError vs -1 is a question of taste. Personally, I'd return * 1 if not found (if the receiver didn't care). Or throw a KeyError. The -1 isn't obviously useful.
Last iteration, making the unit 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With