Grouping constants in Python [closed]
Asked Answered
D

6

15

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?

Dorrisdorry answered 22/4, 2015 at 8:46 Comment(5)
You could also have a class Animal with dog and cat instances of that class.Insalivate
Or you could also use named tuples.Insalivate
How will you use them in your code? Will you iterate or select? Where would the selection parameter come from?Twine
No iteration nor anything like that. Just calling my constant with the full name. Either DOG_NUMBER_OF_LEGS , ANIMALS_CONFIG["DOG"]["NUMBER_OF_LEGS"] or Dog.NUMBER_OF_LEGSDorrisdorry
@MarioA.CorcheroJiménez just to confirm, the only behaviour you want from the the objects is to hold those two values and access them by key and/or attribute?Refinement
R
12

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
Refinement answered 22/4, 2015 at 10:21 Comment(0)
V
4

There's no one-size-fits-all answer, it really depends on how many constants you have to handle, how you use them, if having polymorphism dispatch makes sense or not and the phase of the moon.

Now in your example, since you have two or more sets of constants with a similar structure and meaning, I'd go for the "classes as namespaces" solution.

FWIW preventing a class from being instanciated is not that difficult:

>>> class Foo(object):
...    def __new__(cls):
...       return cls
... 
>>> Foo()
<class '__main__.Foo'>

But documentation should be enough - remember that your "constants" are only constants by convention anyway.

Veronikaveronike answered 22/4, 2015 at 9:21 Comment(0)
E
2

in more recent versions (>= 3.7) i would suggest you use a dataclass for that and making it frozen. This not only prevents any of the members from being changed but it also allows the constants to be used as dictionary keys (as they become hashable).

and in python >= 3.10 you may even want to set slots=True which prevents the addition of new members.

this way you also get the typing right:

from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class Animal:
    number_of_legs: int
    favourite_foods: tuple[str, ...]

DOG = Animal(number_of_legs=4, favourite_foods=('Socks', 'Meat'))
CAT = Animal(number_of_legs=4, favourite_foods=('Lasagna', 'Fish'))

print(CAT)                # Animal(number_of_legs=4, favourite_foods=('Lasagna', 'Fish'))
print(DOG.number_of_legs) # 4
Exequatur answered 2/3, 2023 at 7:27 Comment(0)
T
0

I'd go for the dictionary approach, as it feels more readable and better organized, but I believe it's a matter of personal preference.

Using classes also doesn't seem like a bad idea if you use a class with only static variables and methods like this and you never instantiate them.

Transpose answered 22/4, 2015 at 8:55 Comment(0)
L
0

Another way of using NamedTuples:

If there are some set of constants and its always nice to group them. It will be even more nice, if we can access them on IDE as we type them. Dictionary is not a solution as IDE cannot show recommendations as you type.

from collections import namedtuple
    
_coverage_comments = namedtuple('COVERAGE_COMMENTS',['no_progress', 'coverage_improved'])
    
COVERAGE_COMMENTS = _coverage_comments(no_progress="No Progress", coverage_improved="Improved")
    
print(COVERAGE_COMMENTS.coverage_improved)
Laurettelauri answered 12/8, 2022 at 16:12 Comment(0)
W
0

You can use enum.Enum to group a collection of constants. It has advantages over regular class, dataclass, or namedtuple:

  1. The value of enum items cannot be reassigned at runtime, which makes it truly constant;
  2. You cannot instantiate an object of an enum class, which means it behaves like a singleton.

Moreover, enum items are iterable (list) and hashable (in). You can access the actual constant values through the .value property, which is a bit verbose, though.

from enum import Enum

class DogConstant(Enum):
  NUMBER_OF_LEGS = 4
  FAVOURITE_FOOD = ["Socks", "Meat"]

class CatConstant(Enum):
  NUMBER_OF_LEGS = 4
  FAVOURITE_FOOD = ["Lasagna", "Fish"]

# List all constants
print(f"{list(DogConstant)}")

# Check if an enum item exists in the enum class
print(f"{'NUMBER_OF_LEGS' in DogConstant.__members__ = }")

# Get value of an enum item
print(f"{DogConstant.NUMBER_OF_LEGS.value = }")
print(f"{DogConstant.FAVOURITE_FOOD.value = }")
print(f"{CatConstant.FAVOURITE_FOOD.value = }")

prints

[<DogConstant.NUMBER_OF_LEGS: 4>, <DogConstant.FAVOURITE_FOOD: ['Socks', 'Meat']>]
'NUMBER_OF_LEGS' in DogConstant.__members__ = True
DogConstant.NUMBER_OF_LEGS.value = 4
DogConstant.FAVOURITE_FOOD.value = ['Socks', 'Meat']
CatConstant.FAVOURITE_FOOD.value = ['Lasagna', 'Fish']
Wagtail answered 20/9, 2023 at 21:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.