27

Given the following enum:

class MyEnum(IntEnum):

    A = 0
    B = 1
    C = 2

How can I specify a default value. I want to be able to do:

my_val = MyEnum()

and havemy_val be <MyEnum.A: 0>

Is this possible? I've tried customizing __new__, __init__, __call__ but I can't get it to work.

5
  • You want my_val to be the enum value A? Don't you want my_val to be an instance of MyEnum? Commented Jul 2, 2017 at 5:13
  • @ChristianDean I want to do my_val = MyEnum() and have the result be the same as doing my_val = MyEnum(0) Commented Jul 2, 2017 at 5:17
  • 2
    Do you want something like this? ideone.com/mKk5nF or ideone.com/mfW9Jr Commented Jul 2, 2017 at 5:49
  • @falsetru Yup, that second link is exactly what I needed. Commented Jul 2, 2017 at 6:00
  • You may use _missing_ function; According to the documentation" _missing_ – a lookup function used when a value is not found; may be overridden" Commented Oct 10, 2021 at 11:43

5 Answers 5

53

falsetru's answer would be the way to go if 0 argument case needs to be catered.

If it is only about returning default when given value does not exist, we can override _missing_ hook in Enum class (Since Python 3.6):

from enum import IntEnum


class MyEnum(IntEnum):
    A = 0
    B = 1
    C = 2

    @classmethod
    def _missing_(cls, value):
        return cls.A

assert MyEnum(0) is MyEnum.A
assert MyEnum(1) is MyEnum.B
assert MyEnum(-1) is MyEnum.A
assert MyEnum(None) is MyEnum.A
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for mentioning the _missing_ method. Unfortunately this does not solve the problem. MyEnum() still results in a TypeError. The goal was to be able to make an instance of MyEnum without providing a parameter.
Since EnumMeta __call__ must take a positional argument, only way in that case is to define your metaclass, like what falsetru did.
Is _missing_ a documented feature or just a hidden implementation detail? I mean is it possible that this behavior can change without any announcement in the future Python versions? I'm asking because of that single underscores around the name.
@Stephen why not put a default None for the value parameter?
@KonstantinSmolyanin and others with the same question: it's a documented feature
15

MyEnum(..) is handled by EnumMeta.__call__. You need to override that method:

from enum import EnumMeta, IntEnum


class DefaultEnumMeta(EnumMeta):
    default = object()

    def __call__(cls, value=default, *args, **kwargs):
        if value is DefaultEnumMeta.default:
            # Assume the first enum is default
            return next(iter(cls))
        return super().__call__(value, *args, **kwargs)
        # return super(DefaultEnumMeta, cls).__call__(value, *args, **kwargs) # PY2


class MyEnum(IntEnum, metaclass=DefaultEnumMeta):
    # __metaclass__ = DefaultEnumMeta  # PY2 with enum34
    A = 0
    B = 1
    C = 2


assert MyEnum() is MyEnum.A
assert MyEnum(0) is MyEnum.A
assert MyEnum(1) is not MyEnum.A

4 Comments

Seems like serious overkill for such a simple problem.
@EthanFurman, Yes, it is. But I couldn't come up with a solution that satisfy MyEnum() is MyEnum.A
@EthanFurman I agree that this seems like serious overkill to solve this, but for my use-case, this is way cleaner than the alternative.
Can simplify this slightly by removing default and changing sig to def __call__(cls, *args, **kwargs) then just check if args was empty: if not args: return next(iter(cls))
9

Update

As of aenum 2.2.11 calling an Enum without a value is supported:

from aenum import Enum, no_arg

class Color(Enum):
    black = 0
    red = 1
    green = 2
    blue = 3
    #
    @classmethod
    def _missing_value_(cls, value):
        if value is no_arg:
            return cls.black

and in use:

>>> Color(1)
<Color.red: 1>

>>> Color()
<Color.black: 0>

>>> Color(4)
Traceback (most recent call last):
  ...
ValueError: 4 is not a valid Color

Original Answer

If you do not control the Enums, then do:

my_val = list(MyEnum)[0]

or, if the Enum might be empty:

my_val = len(MyEnum) and list(MyEnum)[0] or None

If you do control the Enums, then add a get_default method:

class MyEnum(Enum):
    def get_default(self):
        return self.A

or, even simpler, make default be an alias:

class MyEnum(Enum):
    A = 0
    B = 1
    C = 2
    default = A

You could also wrap the first method in to a function:

def default_member(enum):
    return list(enum)[0]

1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

4 Comments

I'm familiar with the Enum API. I can see why you are suggesting these, but this doesn't actually answer my question does it ...
@Stephen: falsetru already answered your exact question. I'm offering alternative ways to get what you said you needed (a default member).
My bad. When I looked at this, I thought you had answered before falsetru. Your suggestions are valid if you are looking for ways set Enums to default values for cases where you don't need to call the Enum class without arguments.
@Stephen: Thank you. falsetru's answer is the only way to solve your exact question; just be careful using/modifying metaclasses in general, and the Enum metaclass specifically; I have it doing a lot behind the scenes to make everything work and I've seen casual modifications break resulting Enums more than once.
1

Why don’t you just use the standard syntax?

my_val = MyEnum.A

If you really want to do this, you may have to write your own enum meta override class. You can see this example for the implementation in cpython so that you assign a default value equal to the first value in the value map for the enum.

5 Comments

I'm doing code generation in a metaclass and it would make things quite a bit simpler if I didn't have to know anything about the enum in order to make an instance of it.
@Stephen But wouldn't doing MyEnum(default_value) work? If you don't anything about the enum ahead of time, just get a default value from it.
@ChristianDean I want the author of the enum to specify the default. The metaclass won't know ahead of time what the default is.
There are also some other reasons why being able to call the Enum class without arguments will make things cleaner, hence my question.
@Stephen Can you post the code for your metaclass or an MCVE if the code cannot be taken out of context? If so, I think we can show you how to accomplish what your asking.
1

I also stumbled upon this problem. Posting my solution for Python 3.9.5

from enum import unique, Enum, EnumMeta

class PrinterModelMeta(EnumMeta):
    def __call__(cls, value=-1, *args, **kwargs):
        if value == -1:
            value = PrinterModel.detect_model()
        return super().__call__(value, *args, **kwargs)


@unique
class PrinterModel(Enum, metaclass=PrinterModelMeta):
    # FORBIDDEN = -1
    MODEL0 = 0
    MODEL1 = 1
    MODEL2 = 2

    @classmethod
    def detect_model(cls) -> int:
        model = None
        panel_name = cls.panel_name()
        if panel_name == "model-aaa":
            model = PrinterModel.MODEL0
        if panel_name == "model-bbb":
            model = PrinterModel.MODEL1
        if panel_name == "model-ccc":
            model = PrinterModel.MODEL2

        logger = logging.getLogger(__name__)
        if model is None:
            raise UnknownPrinterModel()
        else:
            return model.value

    @classmethod
    def panel_name(cls) -> str:
        path = Path("/sys/bus/i2c/devices/1-000f/of_node") / "panel-name"
        return path.read_text()[:-1] if path.exists() else None

Comments

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.