Python Lazy Enum

Viewed 380

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?

1 Answers

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'
Related