I'm trying to create an enum.Enum with lazy evaluation.
According to the docs:
An enumeration is a set of symbolic names (members) bound to unique, constant values. Within an enumeration, the members can be compared by identity, and the enumeration itself can be iterated over.
I would say my case still falls within this definition. The values are still unique and constant, I would just like them to be initialised only when necessary. This could be because initialisation is time-consuming, relies on other Enum members, has circular dependencies with other Enums, or (in my current case) because initialisation requires a running QGuiApplication, and I would like my module to still be importable (but keep that specific enum unusable) if one is not running (there are of course other ways to get around this specific requirement, but I'd like to implement this Enum class regardless).
Since we are only supposed to subclass EnumMeta in rare cases, I wanted to do this by subclassing Enum itself:
class Lazy:
def __init__(self, value):
self.value = value
class LazyEnum(Enum):
def __getattribute__(self, name):
result = super().__getattribute__(name)
if name == 'value' and isinstance(result, Lazy):
result = self._lazy_init(result.value)
self.__setattr__(name, result)
return result
The idea is to mark some values as Lazy to be initialised later. However, my sample code:
class MyEnum(LazyEnum):
X = Lazy('x')
Y = Lazy('y')
Z = 'z'
def _lazy_init(self, value):
print(f"Evaluating {value}")
return value * 2
print(MyEnum.X.value)
print(MyEnum.X)
print(MyEnum.X.value)
raises the error:
AttributeError: can't set attribute
How would I get around this issue?
I looked at the source code of Enum and I solved this issue by setting the _value_ attribute directly, rather than the value property. The solution feels somewhat hacky to me, but it seems to work:
from abc import ABCMeta, abstractmethod
from enum import Enum, EnumMeta
class AbstractEnumMeta(EnumMeta, ABCMeta):
pass
class Lazy:
def __init__(self, *lazy_init_arguments):
self.args = lazy_init_arguments
class LazyEnum(Enum, metaclass=AbstractEnumMeta):
def __getattribute__(self, name):
result = super().__getattribute__(name)
if name == 'value' and isinstance(result, Lazy):
result = self._lazy_init(*result.args)
setattr(self, '_value_', result)
return result
@classmethod
@abstractmethod
def _lazy_init(cls, *args):
return args
class MyEnum(LazyEnum):
X = Lazy('x')
Y = Lazy('y')
Z = 'z'
@classmethod
def _lazy_init(cls, value):
print(f"Lazy init of {value}")
return value * 2
>>> MyEnum.Z
MyEnum.Z
>>> MyEnum.Z.value
'z'
>>> MyEnum.X
MyEnum.X
>>> MyEnum.X.value
Lazy init of x
'xx'
>>> MyEnum.X.value
'xx'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With