Reputation: 5575
My module uses constants that I feel should be grouped. Dog and cat have a number of legs and favorite foods.
I thought about:
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.
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.
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?
Upvotes: 16
Views: 11238
Reputation: 547
You can use enum.Enum
to group a collection of constants. It has advantages over regular class
, dataclass
, or namedtuple
:
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']
Upvotes: 1
Reputation: 46901
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
Upvotes: 2
Reputation: 345
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)
Upvotes: 0
Reputation: 122116
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']
namedtuple
s, like vanilla tuple
s, are immutable, too, so you can't accidentally reassign e.g. CAT.number_of_legs = 2
somewhere.
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
Upvotes: 12
Reputation: 77912
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.
Upvotes: 4
Reputation: 321
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.
Upvotes: 1