Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Lazy Enum

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?

like image 329
Mate de Vita Avatar asked Oct 21 '25 20:10

Mate de Vita


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'
like image 58
Mate de Vita Avatar answered Oct 23 '25 11:10

Mate de Vita



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!