EvilTosha
EvilTosha

Reputation: 167

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:

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?

Upvotes: 8

Views: 1941

Answers (2)

bherbruck
bherbruck

Reputation: 2226

A few ways you can do this, I don't really like using enum in python because you dont really need them IMO ;)

This is how most packages out there do it AFAIK:

# module_name.py
CSV = 'csv'
JSON = 'json'

def save(path, format=CSV):
    # do some thing with format
    ...

# other_module.py
import module_name

module_name.save('my_path', fomat=module_name.CSV)

another way is like this:

# module_name.py
options = {
    'csv': some_csv_processing_function
    'json': some_json_processing_function
}

def save(path, format=options.['csv']:
    # do some thing with format
    ...

# other_module.py
import module_name

module_name.save('my_path', fomat=module_name.options['csv'])

(kinda unrelated) You can also make your dicts classes:

class DictClass:
    def __init__(self, dict_class):
        self.__dict__ = dict_class

options = DictClass({
    'csv': some_csv_processing_function
    'json': some_json_processing_function
})

now you can access your dictionary as an object like: options.csv

Upvotes: 1

Ethan Furman
Ethan Furman

Reputation: 69200

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'>,
]

Upvotes: 10

Related Questions