Search code examples
pythonenumspython-class

Group related constants in Python


I'm looking for a pythonic way to define multiple related constants in a single file to be used in multiple modules. I came up with multiple options, but all of them have downsides.

Approach 1 - simple global constants

# file resources/resource_ids.py

FOO_RESOURCE = 'foo'
BAR_RESOURCE = 'bar'
BAZ_RESOURCE = 'baz'
QUX_RESOURCE = 'qux'
# file runtime/bar_handler.py

from resources.resource_ids import BAR_RESOURCE

# ...

def my_code():
  value = get_resource(BAR_RESOURCE)

This is simple and universal, but has a few downsides:

  • _RESOURCE has to be appended to all constant names to provide context
  • Inspecting the constant name in IDE will not display other constant values

Approach 2 - enum

# file resources/resource_ids.py

from enum import Enum, unique

@unique
class ResourceIds(Enum):
  foo = 'foo'
  bar = 'bar'
  baz = 'baz'
  qux = 'qux'
# file runtime/bar_handler.py

from resources.resource_ids import ResourceIds

# ...

def my_code():
  value = get_resource(ResourceIds.bar.value)

This solves the problems of the first approach, but the downside of this solution is the need of using .value in order to get the string representation (assuming we need the string value and not just a consistent enum value). Failure to append .value can result in hard to debug issues in runtime.

Approach 3 - class variables

# file resources/resource_ids.py

class ResourceIds:
  foo = 'foo'
  bar = 'bar'
  baz = 'baz'
  qux = 'qux'
# file runtime/bar_handler.py

from resources.resource_ids import ResourceIds

# ...

def my_code():
  value = get_resource(ResourceIds.bar)

This approach is my favorite, but it may be misinterpreted - classes are made to be instantiated. And while code correctness wouldn't suffer from using an instance of the class instead of the class itself, I would like to avoid this waste.

Another disadvantage of this approach that the values are not actually constant. Any code client can potentially change them.

Is it possible to prevent a class from being instantiated? Am I missing some idiomatic way of grouping closely related constants?


Solution

  • Use Enum and mix in str:

    @unique
    class ResourceIds(str, Enum):
        foo = 'foo'
        bar = 'bar'
        baz = 'baz'
        qux = 'qux'
    

    Then you won't need to compare against .value:

    >>> ResourceIds.foo == 'foo'
    True
    

    And you still get good debugging info:

    >>> ResourceIds.foo
    <ResourceIds.foo: 'foo'>
    
    >>> list(ResourceIds.foo.__class__)
    [
     <ResourceIds.foo: 'foo'>,
     <ResourceIds.bar: 'bar'>,
     <ResourceIds.baz: 'baz'>,
     <ResourceIds.qux: 'qux'>,
    ]