【问题标题】:Is there a way to specify a default value for python enums?有没有办法为 python 枚举指定默认值?
【发布时间】:2017-12-05 15:52:33
【问题描述】:

给定以下枚举:

class MyEnum(IntEnum):

    A = 0
    B = 1
    C = 2

如何指定默认值。我希望能够做到:

my_val = MyEnum()

并且有my_val<MyEnum.A: 0>

这可能吗?我试过自定义__new____init____call__,但我无法让它工作。

【问题讨论】:

  • 您希望my_val 成为枚举值A?你不想让my_val 成为MyEnum 的一个实例吗?
  • @ChristianDean 我想做my_val = MyEnum(),结果和my_val = MyEnum(0)一样
  • 你想要这样的东西吗? ideone.com/mKk5nFideone.com/mfW9Jr
  • @falsetru 是的,第二个链接正是我需要的。
  • 你可以使用_missing_函数;根据documentation"_missing_ – 在找不到值时使用的查找函数;可能会被覆盖"

标签: python enums


【解决方案1】:

falsetru's answer 将是如果需要满足 0 参数情况的方法。

如果只是在给定值不存在时返回默认值,我们可以覆盖 Enum 类中的_missing_ 钩子(由于Python 3.6):

from enum import IntEnum


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

    @classmethod
    def _missing_(cls, value):
        return MyEnum.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

【讨论】:

  • 感谢您提及_missing_ 方法。不幸的是,这并不能解决问题。 MyEnum() 仍然会产生 TypeError。目标是能够在不提供参数的情况下创建MyEnum 的实例。
  • 由于 EnumMeta __call__ 必须采用位置参数,在这种情况下唯一的方法是定义您的元类,就像 falsetru 所做的那样。
【解决方案2】:

MyEnum(..)EnumMeta.__call__ 处理。您需要覆盖该方法:

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

【讨论】:

  • 对于这样一个简单的问题来说似乎是严重的矫枉过正。
  • @EthanFurman,是的。但我想不出满足MyEnum() is MyEnum.A的解决方案
  • @EthanFurman 我同意这似乎是解决这个问题的严重矫枉过正,但对于我的用例,这方式比替代方式更干净。
【解决方案3】:

更新

截至aenum 2.2.11 支持调用不带值的枚举:

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

并在使用中:

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

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

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

原答案

如果你不控制Enums,那么做:

my_val = list(MyEnum)[0]

或者,如果Enum 可能为空:

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

如果你确实控制了Enums,那么添加一个get_default 方法:

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

或者,更简单,将default 设为别名:

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

您也可以将第一个方法包装到一个函数中:

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

1 披露:我是Python stdlib Enumenum34 backportAdvanced Enumeration (aenum) 库的作者。

【讨论】:

  • 我熟悉 Enum API。我明白你为什么要建议这些,但这实际上并不能回答我的问题……
  • @Stephen:falsetru 已经回答了您的确切问题。我正在提供替代方法来获得您所说的所需内容(默认成员)。
  • 我的错。当我看到这个时,我以为你在 falsetru 之前已经回答了。如果您正在寻找将 Enums 设置为默认值的方法,那么您的建议是有效的,因为您不需要在没有参数的情况下调用 Enum 类。
  • @Stephen:谢谢。 falsetru 的回答是解决您确切问题的唯一方法;通常要小心使用/修改元类,特别是Enum 元类;我在幕后做了很多工作以使一切正常运行,而且我不止一次看到随意修改导致Enums 中断。
【解决方案4】:

你为什么不直接使用standard syntax

my_val = MyEnum.A

如果您真的想这样做,您可能必须编写自己的枚举元覆盖类。您可以查看implementation in cpython 的此示例,以便为枚举分配一个等于值映射中第一个值的默认值。

【讨论】:

  • 我在元类中进行代码生成,如果我不需要了解任何有关枚举的信息就可以创建它的实例,这会使事情变得相当简单。
  • @Stephen 但是MyEnum(default_value) 不行吗?如果您提前对枚举不做任何事情,只需从中获取默认值。
  • @ChristianDean 我希望枚举的作者指定默认值。元类不会提前知道默认值是什么。
  • 还有其他一些原因说明为什么能够在没有参数的情况下调用 Enum 类会使事情变得更简洁,因此我的问题是。
  • @Stephen 如果代码不能脱离上下文,您能否发布元类的代码或MCVE?如果是这样,我想我们可以向您展示如何完成您的要求。
【解决方案5】:

我也偶然发现了这个问题。发布我的 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-10-17
    • 2015-03-17
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 2013-05-31
    • 2011-01-31
    • 1970-01-01
    相关资源
    最近更新 更多