39

考虑:

class Item:
   def __init__(self, a, b):
       self.a = a
       self.b = b

class Items:
    GREEN = Item('a', 'b')
    BLUE = Item('c', 'd')

有没有办法让简单枚举的想法适应这种情况?(见这个问题)理想情况下,就像在 Java 中一样,我想把它全部塞进一个类中。

Java模型:

enum EnumWithAttrs {
    GREEN("a", "b"),
    BLUE("c", "d");

    EnumWithAttrs(String a, String b) {
      this.a = a;
      this.b = b;
    }

    private String a;
    private String b;

    /* accessors and other java noise */
}
4

7 回答 7

39

Python 3.4 有一个新的 Enum 数据类型(它已被向后移植enum34增强为aenum1)。enum34aenum2都可以轻松支持您的用例:

  • aenum(Python 2/3)

      import aenum
      class EnumWithAttrs(aenum.AutoNumberEnum):
          _init_ = 'a b'
          GREEN = 'a', 'b'
          BLUE = 'c', 'd'
    
  • enum34(Python 2/3) 或标准库enum(Python 3.4+)

      import enum
      class EnumWithAttrs(enum.Enum):
    
          def __new__(cls, *args, **kwds):
              value = len(cls.__members__) + 1
              obj = object.__new__(cls)
              obj._value_ = value
              return obj
          def __init__(self, a, b):
              self.a = a
              self.b = b
    
          GREEN = 'a', 'b'
          BLUE = 'c', 'd'
    

并在使用中:

>>> EnumWithAttrs.BLUE
<EnumWithAttrs.BLUE: 1>

>>> EnumWithAttrs.BLUE.a
'c'

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

2 aenum还支持NamedConstants基于元类的NamedTuples.

于 2013-10-10T15:44:59.573 回答
30

对于 Python 3:

class Status(Enum):
    READY = "ready", "I'm ready to do whatever is needed"
    ERROR = "error", "Something went wrong here"

    def __new__(cls, *args, **kwds):
        obj = object.__new__(cls)
        obj._value_ = args[0]
        return obj

    # ignore the first param since it's already set by __new__
    def __init__(self, _: str, description: str = None):
        self._description_ = description

    def __str__(self):
        return self.value

    # this makes sure that the description is read-only
    @property
    def description(self):
        return self._description_

您可以按类型将其用作标准枚举或工厂:

print(Status.READY)
# ready
print(Status.READY.description)
# I'm ready to do whatever is needed
print(Status("ready")) # this does not create a new object
# ready
于 2019-02-17T10:08:37.910 回答
27

在 Python 3.4 和添加出色的enum模块之前,一个不错的选择是使用namedtuple

from collections import namedtuple

Item = namedtuple('abitem', ['a', 'b'])

class Items:
    GREEN = Item('a', 'b')
    BLUE = Item('c', 'd')

现在,任何受支持的 Python 版本都有enum,所以请使用该模块。它使您可以更好地控制每个枚举值的生成方式。

如果您给每个项目一个值元组,那么这些值将__init__作为单独的(位置)参数传递给方法,这样您就可以在枚举值上设置其他属性:

from enum import Enum

class Items(Enum):
    GREEN = ('a', 'b')
    BLUE = ('c', 'd')

    def __init__(self, a, b):
        self.a = a
        self.b = b

这将生成枚举条目,其值是分配给每个名称的元组,以及两个属性ab

>>> Items.GREEN, Items.BLUE
(<Items.GREEN: ('a', 'b')>, <Items.BLUE: ('c', 'd')>)
>>> Items.BLUE.a
'c'
>>> Items.BLUE.b
'd'
>>> Items(('a', 'b'))
<Items.GREEN: ('a', 'b')>

请注意,您可以通过再次传入相同的元组来查找每个枚举值。

如果第一项应该代表每个枚举条目的值,请使用一种__new__方法来设置_value_

from enum import Enum

class Items(Enum):
    GREEN = ('a', 'b')
    BLUE = ('c', 'd')

    def __new__(cls, a, b):
        entry = object.__new__(cls) 
        entry.a = entry._value_ = a  # set the value, and the extra attribute
        entry.b = b
        return entry

    def __repr__(self):
        return f'<{type(self).__name__}.{self.name}: ({self.a!r}, {self.b!r})>'

我也添加了一个自定义__repr__,默认只包括self._value_. 现在每个条目的值由元组中的第一项定义,可用于查找枚举条目:

>>> Items.GREEN, Items.BLUE
(<Items.GREEN: ('a', 'b')>, <Items.BLUE: ('c', 'd')>)
>>> Items.BLUE.a
'c'
>>> Items.BLUE.b
'd'
>>> Items('a')
<Items.GREEN: ('a', 'b')>

有关更多选项,请参阅文档中的vs.部分。__init____new__

于 2012-10-01T19:58:15.713 回答
16

这是另一种方法,我认为它比其他方法更简单,但具有最大的灵活性:

from collections import namedtuple
from enum import Enum

class Status(namedtuple('Status', 'name description'), Enum):
    READY = 'ready', 'I am ready to do whatever is needed'
    ERROR = 'error', 'Something went wrong here'

    def __str__(self) -> str:
        return self.name

按预期工作:

print(Status.READY)
print(repr(Status.READY))
print(Status.READY.description)
print(Status.READY.value)

印刷:

ready
<Status.READY: Status(name='ready', description='I am ready to do whatever is needed')>
I am ready to do whatever is needed
Status(name='ready', description='I am ready to do whatever is needed')

你会得到最好的namedtuple Enum

于 2020-06-26T18:50:12.370 回答
2

对于小型枚举@property 可能会起作用:

class WikiCfpEntry(Enum):
    '''
    possible supported storage modes
    '''
    EVENT = "Event"      
    SERIES = "Series"
    
    @property
    def urlPrefix(self):
        baseUrl="http://www.wikicfp.com/cfp"
        if self==WikiCfpEntry.EVENT:
            url= f"{baseUrl}/servlet/event.showcfp?eventid="
        elif self==WikiCfpEntry.SERIES:
            url= f"{baseUrl}/program?id="
        return url
于 2021-07-25T17:04:43.233 回答
1

对于基于关键字的属性初始化,您可以尝试data-enum,这是一种更轻量级的 enum 实现,在某些情况下具有更简洁的语法,包括本例。

from data_enum import DataEnum

class Item(DataEnum):
    data_attribute_names = ('a', 'b')

Item.GREEN = Item(a='a', b='b')
Item.BLUE = Item(a='c', b='d')

我应该注意,我是 data-enum 的作者,并专门构建它来解决这个用例。

于 2021-09-01T15:05:47.340 回答
1

受到其他一些答案的启发,我找到了一种方法,可以尽可能“透明地”将附加字段包含到枚举中,从而克服了其他方法的一些缺点。一切都像没有附加字段一样工作。

枚举就像一个元组一样是不可变的,枚举的值就像没有附加字段一样,它就像一个普通的枚举一样工作auto(),并且按值选择一个枚举是可行的。

import enum

# Common base class for all enums you want to create with additional fields (you only need this once)
class EnumFI(enum.Enum):

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls._values = []

    def __new__(cls, *args, **kwargs):
        value = args[0]
        if isinstance(value, enum.auto):
            if value.value == enum._auto_null:
                value.value = cls._generate_next_value_(None, 1, len(cls.__members__), cls._values[:])  # Note: This just passes None for the key, which is generally okay
            value = value.value
            args = (value,) + args[1:]
        cls._values.append(value)
        instance = cls._member_type_.__new__(cls, *args, **kwargs)
        instance._value_ = value
        return instance

    def __format__(self, format_spec):
        return str.__format__(str(self), format_spec)

然后在代码中的任何地方你都可以这样做:

from enum import auto
from collections import namedtuple

class Color(namedtuple('ColorTuple', 'id r g b'), EnumFI):
    GREEN = auto(), 0, 255, 0
    BLUE = auto(), 0, 0, 255

示例输出:

In[4]: Color.GREEN
Out[4]: <Color.GREEN: 1>

In[5]: Color.GREEN.value
Out[5]: 1

In[6]: Color.GREEN.r
Out[6]: 0

In[7]: Color.GREEN.g
Out[7]: 255

In[8]: Color.GREEN.b
Out[8]: 0

In[9]: Color.GREEN.r = 8
Traceback (most recent call last):
  File "/home/phil/anaconda3/envs/dl/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-9-914a059d9d3b>", line 1, in <module>
    Color.GREEN.r = 8
AttributeError: can't set attribute

In[10]: Color(2)
Out[10]: <Color.BLUE: 2>

In[11]: Color['BLUE']
Out[11]: <Color.BLUE: 2>
于 2021-06-03T14:55:45.550 回答