2

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?

EvilTosha
  • 107
  • 9

2 Answers2

4

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'>,
]
Ethan Furman
  • 52,296
  • 16
  • 127
  • 201
  • Nice! And it even works with dictionaries: `d = {ResourceIds.foo: 'foo_value'} print(d['foo']) # prints "foo_value" ` – EvilTosha May 06 '20 at 02:12
1

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

bherbruck
  • 1,821
  • 1
  • 4
  • 14
  • Good to know, thanks for providing the context! Do you think these packages use constants and not enums because they were developed back when enums were not a thing? – EvilTosha May 06 '20 at 02:17