Recipe 6.1. Converting Among Temperature Scales
Credit: Artur de Sousa Rocha, Adde Nilsson
Problem
You want to convert easily among Kelvin,
Celsius, Fahrenheit, and Rankine scales of
temperature.
Solution
Rather than having a dozen functions to do all possible conversions,
we can more elegantly package this functionality into a class:
class Temperature(object):
coefficients = {'c': (1.0, 0.0, -273.15), 'f': (1.8, -273.15, 32.0),
'r': (1.8, 0.0, 0.0)}
def _ _init_ _(self, **kwargs):
# default to absolute (Kelvin) 0, but allow one named argument,
# with name being k, c, f or r, to use any of the scales
try:
name, value = kwargs.popitem( )
except KeyError:
# no arguments, so default to k=0
name, value = 'k', 0
# error if there are more arguments, or the arg's name is unknown
if kwargs or name not in 'kcfr':
kwargs[name] = value # put it back for diagnosis
raise TypeError, 'invalid arguments %r' % kwargs
setattr(self, name, float(value))
def _ _getattr_ _(self, name):
# maps getting of c, f, r, to computation from k
try:
eq = self.coefficients[name]
except KeyError:
# unknown name, give error message
raise AttributeError, name
return (self.k + eq[1]) * eq[0] + eq[2]
def _ _setattr_ _(self, name, value):
# maps settings of k, c, f, r, to setting of k; forbids others
if name in self.coefficients:
# name is c, f or r -- compute and set k
eq = self.coefficients[name]
self.k = (value - eq[2]) / eq[0] - eq[1]
elif name == 'k':
# name is k, just set it
object._ _setattr_ _(self, name, value)
else:
# unknown name, give error message
raise AttributeError, name
def _ _str_ _(self):
# readable, concise representation as string
return "%s K" % self.k
def _ _repr_ _(self):
# detailed, precise representation as string
return "Temperature(k=%r)" % self.k
Discussion
Converting between several different scales or units of measure is a
task that's subject to a
"combinatorial explosion": if we
tackle it in the apparently obvious way, by providing a function for
each conversion, then, to deal with n
different units, we will have to write n *
(n-1) functions.A Python class can intercept attribute setting and getting, and
perform computation on the fly in response. This power enables a much
handier and more elegant architecture, as shown in this recipe for
the specific case of temperatures.Inside the class, we always hold the measurement in one reference
unit or scale, Kelvin (absolute) degrees in the case of this recipe.
We allow the setting of the value to happen through any of four
attribute names ('k', 'r', 'c', 'f', abbreviations
of the scales' names), and compute and set the
Kelvin-scale value appropriately. Vice versa, we also allow the
"getting" of the value in any
scale, through the same attribute names, computing the result on the
fly. (Assuming you have saved the code in this recipe as
te.py somewhere on your Python
sys.path, you can import it as a module.) For
example:
>>> from te import Temperature_ _getattr_ _
>>> t = Temperature(f=70) # 70 F is...
>>> print t.c # ...a bit over 21 C
21.1111111111
>>> t.c = 23 # 23 C is...
>>> print t.f # ...a bit over 73 F
73.4
and _ _setattr_ _
work better than named properties would in this case, since the form
of the computation is the same for every attribute (except the
reference 'k' one), and we only need to use
different coefficients that we can most handily keep in a per-class
dictionary, the one we name self.coefficients.
It's important to remember that _ _setattr_
_ is called on every setting of any
attribute, so it must delegate to object the
setting of attributes, which need to be recorded in the instance (the
_ _setattr_ _ implementation in this recipe does
just such a delegation for attribute k) and must
raise an AttributeError exception for attributes
that can't be set. _ _getattr_ _,
on the other hand, is called only upon the
"getting" of an attribute that
can't be found by other,
"normal" means (e.g., in the case
of this recipe's class, _ _getattr_
_ is not called for accesses to
attribute k, which is recorded in the instance and
thus gets found by normal means). _ _getattr_ _
must also raise an AttributeError exception for
attributes that can't be accessed.
See Also
Library Reference and Python in a
Nutshell documentation on attributes and on special
methods _ _getattr_ _ and _ _setattr_
_.