Search code examples
pythonpython-2.7namespacescoding-styleconstants

Grouping constants in Python


My module uses constants that I feel should be grouped. Dog and cat have a number of legs and favorite foods.

  1. I want to model nothing but those constants about dogs and cats.
  2. Likely I'll have more animals in the future.
  3. Those constants won't be used outside of the module.

I thought about:

  1. Constants at module level:

    DOG_NUMBER_OF_LEGS = 4
    DOG_FAVOURITE_FOOD = ["Socks", "Meat"]
    CAT_NUMBER_OF_LEGS = 4
    CAT_FAVOURITE_FOOD = ["Lasagna", "Fish"]
    

    They seem not grouped, but it is the solution I prefer.

  2. Classes as namespaces:

    class Dog(object):
      NUMBER_OF_LEGS = 4
      DOG_FAVOURITE_FOOD = ["Socks", "Meat"]
    class Cat(object):
      NUMBER_OF_LEGS = 4
      FAVOURITE_FOOD = ["Lasagna", "Fish"]
    

    I don't like this as they're classes I won't use but can be actually instantiated.

  3. Dictionary of constants:

    ANIMALS_CONFIG = {
       "DOG" : {
         "NUMBER_OF_LEGS" : 4,
         "FAVOURITE_FOOD" : ["Socks", "Meat"]
       },
       "CAT" : {
         "NUMBER_OF_LEGS" : 4,
         "FAVOURITE_FOOD" : ["Lasagna", "Fish"]
       }
    }
    

I also thought about adding submodules but I don't want to expose those internal constants. What is the most pythonic way or how would you do it?


Solution

  • I would go for a fourth option, preferring a collections.namedtuple:

    Animal = namedtuple('Animal', 'number_of_legs favourite_food')
    

    You then create instances like:

    DOG = Animal(4, ['Socks', 'Meat'])
    CAT = Animal(4, ['Lasagna', 'Fish'])
    

    and access the values externally as:

    from animals import CAT
    
    print CAT.number_of_legs
    

    There's really no point having classes if you don't need to create any methods, and I think the form of access above is neater than e.g.:

    from animals import animals
    
    print animals['CAT']['number_of_legs']
    

    namedtuples, like vanilla tuples, are immutable, too, so you can't accidentally reassign e.g. CAT.number_of_legs = 2somewhere.

    Finally, the namedtuple is a lightweight data structure, which may be important if you're creating lots of animals:

    >>> import sys
    >>> sys.getsizeof({'number_of_legs': 4, 'favourite_food': ['Lasagna', 'Fish']})
    140
    >>> from collections import namedtuple
    >>> Animal = namedtuple('Animal', 'number_of_legs favourite_food')
    >>> sys.getsizeof(Animal(4, ['Lasagna', 'Fish']))
    36