The built-in enum
provides a way to create enums of primitive types (IntEnum, StrEnum).
I'd like to create an enum of structured objects.
One way to do that is with dataclass
, and it works:
from dataclasses import dataclass
from enum import Enum
from typing_extensions import assert_never
@dataclass(frozen=True)
class _BaseInstTypeDataclass:
instance_type: str
display_name: str
class InstTypeDataClass(_BaseInstTypeDataclass, Enum):
t2_micro = ("t2.micro", "t2.micro: Cheap!")
r7i_2xlarge = ("r7i.2xlarge", "r7i.2xlarge: Expensive!")
assert list(InstTypeDataClass) == [
InstTypeDataClass.t2_micro,
InstTypeDataClass.r7i_2xlarge,
]
assert isinstance(InstTypeDataClass.t2_micro, InstTypeDataClass)
# This function type checks
def f_dataclass(e: InstTypeDataClass):
if e == InstTypeDataClass.t2_micro:
...
elif e == InstTypeDataClass.r7i_2xlarge:
...
else:
assert_never(e)
Both the static- (via pyright) and runtime-behavior is as expected.
However, with attrs...:
import attrs
@attrs.define(frozen=True)
class _BaseInstTypeAttrs:
instance_type: str
display_name: str
class InstTypeAttrs(_BaseInstTypeAttrs, Enum):
t2_micro = ("t2.micro", "t2.micro: Cheap!")
r7i_2xlarge = ("r7i.2xlarge", "r7i.2xlarge: Expensive!")
# This function type checks
def f_attrs(e: InstTypeAttrs):
if e == InstTypeAttrs.t2_micro:
...
elif e == InstTypeAttrs.r7i_2xlarge:
...
else:
assert_never(e)
... the type checker is happy, but at run-time...:
$ python foo.py
_BaseInstTypeDataclass(instance_type='foo', display_name='bar')
Traceback (most recent call last):
File "foo.py", line 58, in <module>
class InstTypeAttrs(_BaseInstTypeAttrs, Enum):
File "/Users/.../python3.10/enum.py", line 287, in __new__
enum_member._value_ = value
File "/Users/.../python3.10/site-packages/attr/_make.py", line 551, in _frozen_setattrs
raise FrozenInstanceError()
attr.exceptions.FrozenInstanceError
Is this because... Enum
and attrs.define(frozen=True)
don't play nice...?
Frozen dataclasses only block assignment to fields. Frozen attrs
classes block all attribute assignment (except for a weird special case where the instance is an exception object).
That means that when the enum
internals try to set the enum member's _value_
attribute, the frozen dataclass accepts the assignment, because _value_
isn't a dataclass field. The attrs
-based class rejects the assignment.