Septatrix
Septatrix

Reputation: 204

Enum with infinite/dynamic members

Abstract: I would like to have an (integer) enum which has some (negative) members which correspond to specific states and unbound (positive) members.

Example: Assume we have a database entry which stores the winning position in a race. These entries are positive if the race was finished. However there are also some special values for e.g. Did not finish, Did not attend, Disqualified. These are stored in the database as negative values.

Now I would like to represent this structure in Python. As enums are immutable this turns out to be rather difficult. I already studied enum.EnumMeta and the looked at the aenum library but did not find a solution. My least ridiculous idea was to have an enum like the following:

import enum

class RankingPlace(enum.IntEnum):
    FINISHED = 0
    DID_NOT_FINISH = -1
    DID_NOT_ATTEND = -2
    DISQUALIFIED = -3

    def __call__(self, value):
        if value < 0:
            return super()(member)
        else:
            member = self.__class__.FINISHED
            member.actual_place = value
            return member

This kinda works but I have to make the call on an member to make it work. Is there a cleaner way using e.g. a custom metaclass?

Upvotes: 2

Views: 259

Answers (2)

Septatrix
Septatrix

Reputation: 204

I already considered tuples but wanted to keep everything in a single class as I have to do this for multiple enums and do not like to have two classes for each of these. This is in part due to runtime validation mechanisms we use in our application and trying to keep the code DRY.

However reading @Ethan's answer and the above comments I came up with a clever solution (or so I think) which still allows mypy to properly check the types.

from enum import IntEnum
from typing import Generic, TypeVar

E = TypeVar("E", bound=IntEnum)

# use Generic as mypy does not support generic NamedTuple yet
class InfiniteEnum(Generic[E]):
    def __init__(self, enum: E, int_: int):
        self.enum = enum
        self.int = int_

class Status(IntEnum):
    FINISHED = 0
    DID_NOT_FINISH = -1
    DID_NOT_ATTEND = -2
    DISQUALIFIED = -3

winner = InfiniteEnum[Status](Status.FINISHED, 1)

If one only needs this once the answer I accepted is probably cleaner.

Upvotes: 0

Ethan Furman
Ethan Furman

Reputation: 69041

It sounds like the best solution would be a combination of Enum and something else -- maybe a namedtuple (example code using NamedTuple from aenum:

class Status(aenum.IntEnum):
    FINISHED = 0
    DID_NOT_FINISH = -1
    DID_NOT_ATTEND = -2
    DISQUALIFIED = -3

class Placement(aenum.NamedTuple):
    race = 0, 'name of race'
    status = 1, 'finished race?'
    placement = 2, 'position when finished'

winner = Placement('2021 Marathon', Status.FINISHED, 1)

Upvotes: 3

Related Questions