Then MY_PATHS is the constant that I wanted, and has some nice properties:
I can use dataclasses.fields to iterate over the fields of the class.
Unlike a dict, I can access values cleanly with MY_PATHS.input_path with tab-completion in my editor instead of needing to guess at the dict key like MY_PATHS["input_path"].
Type checkers can understand and enforce all the type information involved.
But it would be even cleaner if I could just do something like
and have the result that MyPaths is a constant immutable object, allowing me to access MyPaths.inputs directly, and raising an error in case of any attempt to instantiate it or modify its attributes.
I’m not sure if a decorator is the right approach – a signature like class MyPaths(Constant) would be fine as well.
In this scenario I would just use a module of constants? What’s the class-like behavior that you’re using here?
A module has all the benefits and is simpler to use. You get type-checking, tab completion…If you want to iterate over values, you could just define that as another constant (fields = [inputs, outputs, .... etc])
A module of constants is a reasonable alternative. My main concerns with that approach:
It’s easy to add a new constant and then forget to add it to the corresponding fields = [... meta constant – ideally the relationship is programmatically enforced.
In cases where I have several related small groups of constants, the module-of-constants approach would involve creating several tiny modules, which can be a little harder to read/organize/document vs allowing them to all live in the same model, grouped instead under class definitions.
You could programmatically make this in a few ways. Probably the easiest is:
fields = [x for x in globals() if not x.startswith("_")]
This requires the discipline that anything NOT meant to be part of fields has a leading underscore (eg if you import any other modules), but otherwise is completely automatic.
Here is a constant decorator implementation that extends @nedbat’s solution. As shown in the demo,
type hints are now optional within the class declaration – sometimes the type is quite obvious both to the reader and the type checker, i.e. : Path = Path( is redundant.
__iter__ iterates over the name: value pairs, such that you can call dict directly on the class to get what you’d expect
I need to type MyEnum.ATTRIBUTE_NAME.value to access the value of an attribute, where I’d rather have direct access like MyEnum.ATTRIBUTE_NAME as in other objects.
Type checkers are typically blind to the type of the value MyEnum.ATTRIBUTE_NAME.value, at least when the values are set to arbitrary mixed types:
class MixedEnum(Enum):
“““Type checker won’t know the type .value items.”””
STRING_VALUE = “hello”
INT_VALUE = 42
Do you feel like this should become part of the language soon, or should it this issue be marked as resolved (I’d move it to Python Help and mark it as solved)?
The workarounds proposed in this thread are helpful and enabled me to draft a prototype that seems cleaner than any other existing option. Of course my implementation is not at battle-tested, and could be made even cleaner if added to the language natively. So I’d love that, but I can see that’s unlikely to be a community priority soon. So feel free to mark this as resolved – thanks for asking!