Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Other"/Default-name behavior on a python Enum

I'm trying to achieve the following behavior in a python "enum" (so far to no success):

Given the enum class

class MyEnum(enum.Enum):
    A=1
    B=2
    C=3

I want to have an "Other" member such that MyEnum(5) will be interpreted as "Other", while retaining the value 5, or,

>>> str(MyEnum(5))
... "<MyEnum.Other: 5>"

I thought of doing something along the line of overriding the _missing_ function, but I don't know how to create a "custom" instance of MyEnum without rewriting EnumMeta.

Advices will be appreciated.

EDIT: Following some comments that fail to understand my question precisely, I do not wish to have a default value for the enum, as this default value will not retain the value (that I wish to keep). I wish only that the value will be accepted with a default name.

like image 629
EZLearner Avatar asked Oct 28 '25 23:10

EZLearner


1 Answers

As the saying goes, if you want something done... I created the following enum subclass (I didn't add any new members so that's allowed):

class DefaultNameEnum(Enum):
    """Support for "Other"/default-name values"""

    @classmethod
    def _missing_(cls, value):
        possible_member = cls._value2member_map_.get(value, None)

        if possible_member is None:
            possible_member = cls._create_pseudo_member_(value)

        return possible_member

    @classmethod
    def _create_pseudo_member_(cls, value):
        """
        Create a default-name member.
        """

        default_member = cls._value2member_map_.get(None, None)

        if default_member is None:
            raise ValueError("%r is not a valid %s" % (value, cls.__name__))

        # construct a singleton enum pseudo-member
        other_member = object.__new__(cls)
        other_member._name_ = default_member._name_
        other_member._value_ = value

        # use setdefault in case another thread already created a composite
        # with this value
        other_member = cls._value2member_map_.setdefault(value, other_member)

        return other_member

    def __eq__(self, other):
        """Overrides the default implementation"""
        if isinstance(other, DefaultNameEnum):
            return self._name_ == other._name_
        return False

    def __ne__(self, other):
        return not self == other

This is based on the Flag enum subclass. Its usage is quite simple, actually - Just define as None whichever name you wish to have as your default. It is best illustrated using an example - consider the class:

class ABC(DefaultNameEnum):
    A = 1
    B = 2
    C = 3

    Other = None

Than, the following console calls will give:

>>> print([repr(mem) for mem in ABC])    
... ['<ABC.A: 1>', '<ABC.B: 2>', '<ABC.C: 3>', '<ABC.Other: None>']
>>> ABC(123)
... '<ABC.Other: 123>'
>>> ABC(1) == ABC(2)
... False
>>> ABC(123) == ABC.Other
... True
>>> ABC(123) == ABC(1374)
... True

If you wish to take this implementation and use it, note the following points:

  1. The behavior in the last line might be wanted and might not - depending on your usage. If this is an unwanted usage, just change the __eq__ method to compare names when either self._value_ or other._value_ are None.

  2. If you use this class, for the sake of representability you might wish for the default value's __repr__ to output '<ABC.Other>' rather than '<ABC.Other: None>' in the case of None value. This could easily be accomplished by overriding the __repr__ method.

  3. If you don't define a default member, the class will raise an exception upon calling it upon an unknown value (just like any Enum subclass).

I also wish to note that in the above implementation I would've preferred to use a sunder member such as _default_name_ or _default_value_member_ rather than assigning None, but alas the enum module does not permit defining a new sunder member for Enum subclasses.

like image 143
EZLearner Avatar answered Oct 30 '25 16:10

EZLearner



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!