Python convention for variable naming to indicate units

I'd go further and have separate object types providing type safety rather than simply rely on naming conventions. Otherwise you could pass a variable representing inches into a method requiring miles.

I think that relying on naming conventions is going to be problematic to maintain long term and making use of types will give you much more flexibility and safety (e.g. providing conversions etc. built into the object types)


If you want more robust unit support, you should check out PyPi's Pint module. It doesn't directly deal with your question of naming convention, but it can take a lot of the work out of dealing with frequent conversions. You can find info on it and some other unit modules here:

http://www.drdobbs.com/jvm/quantities-and-units-in-python/240161101


...six years later...

Long after my question, I stumbled on the python typing library. It provides a (runtime) free method of static type checking a variable more specifically than just "int" or "float" but instead "inches" and "meters":

from typing import NewType

UserId = NewType('UserId', int)
some_id = UserId(524313)

This gets me most of what I was after.


While you could come up with a naming convention, you might be better served by building an object representing "distance" with properties to read/write in different units. For instance:

class Distance(object):

    def __init__(self):
        self._inches = 0

    @property
    def inches(self):
        return self._inches

    @inches.setter
    def inches(self, value):
        self._inches = value

    @property
    def feet(self):
        return self._inches/12

    @feet.setter
    def feet(self, value):
        self._inches = value * 12

You could even make it more generic, so that you could easily extend with new conversions. (Note: Edited this to memoize based upon comments)

from collections import defaultdict

class Distance(object):

    _conversion_map = defaultdict(lambda: {'to' : None, 'from' : None})

    def __init__(self, **kwargs):
        self._memo = {}
        if kwargs:
            unit, value = kwargs.iteritems().next()
            if unit == 'inches':
                self.inches = value
            else:
                setattr(self, unit, value)
        else:
            self.inches = 0

    def __getattr__(self, name):
        if name in self._conversion_map:
            try:
                return self._memo[name]
            except KeyError:
                converter = self._conversion_map[name]['to']
                if converter is None:
                    raise AttributeError
                converted = converter(self.inches)
                self._memo[name] = converted
                return converted
        else:
            raise AttributeError

    def __setattr__(self, name, value):
        if name == '_memo':
            super(Distance, self).__setattr__(name, value)
        else:
            # Clear memoized values if setting the value of the object
            self._memo = {}
        if name == 'inches':
            super(Distance, self).__setattr__(name, value)
        if name in self._conversion_map:
            converter = self._conversion_map[name]['from']
            if converter is None:
                raise AttributeError
            self._memo[name] = value
            self.inches = converter(value)
        else:
            raise AttributeError

    @classmethod
    def converter(cls, func):
        direction, unit = func.__name__.split('_', 1)
        cls._conversion_map[unit][direction] = func
        return func

@Distance.converter
def to_feet(value):
    return value / 12

@Distance.converter
def from_feet(value):
    return value * 12

board_1_length = Distance(feet=2)
board_2_length = Distance(inches=14)
board_1_length.inches # 24
board_2_length.feet # 1 (integer division)