4

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.

6
  • That does not look like an application for Enum. What exactly are you trying to do? Commented Dec 9, 2019 at 20:15
  • Why not? I'm trying to simplify the use of an internet enumerable set of constants, and have a default assignment given an unknown value. This allows for differentiating between known and unknown data in a simple and coherent way throughout the code, as well as allows for more encapsulated handling of unknown data (say, in a plugin or an outside module). Commented Dec 9, 2019 at 21:09
  • Check out When and where to use Python Enum. It is possible to have a default as shown in my answer here, but the invalid/unknown code is not saved. Commented Dec 9, 2019 at 21:21
  • What you wrote could be accomplished simply by overriding the _missing_ sunder method in Python 3.4+, but a nice answer nonetheless. Commented Dec 10, 2019 at 9:31
  • Does this answer your question? Is there a way to specify a default value for python enums? Commented Dec 10, 2019 at 10:00

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Kudos! I appreciate that your solution does not involve messing with EnumMeta. The docstring for _create_pseudo_member_ is not correct, though. Also, I don't understand how you would have used _default_value_? (Note: you could have used a single leading underscore.)
You are correct about the docstring - I just took the Flag's class implementation of this class as a basis (and hence the last line in this function which I guess solves a bug which I don't understand). I altered the docstring. Thanks!
Regarding _default_value_, I should've used a different naming, so I renamed these in the answer to perhaps clarify better. Two possibilities of additional members come to mind - either _default_name_ that would contain a string representing the name of the "Other"-member; or _default_value_member_ (again, not a good naming) that would contain the value whose enum-label should be the "Other"-label. For example, in this case, add _default_value_member_=None
0

This looks like what you're asking for:

>>> class Yep(enum.IntFlag):
...  A = 1
...  B = 2
... 
>>> Yep(5)
<Yep.4|A: 5>

So, Yep(5) is valid, but there won't be a Yep.Other magic member.

3 Comments

I thought of something of the sort (even though I don't want to restrict myself to integer values), but then I lose the equality-comparison and representabilty features.
@EZLearner, equality comparison works fine: Yep(5) == Yep(5)
Yes, but what I meant is to check if two different "Other"s are similar, and that doesn't happen here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.