我尝试创建一个具有整数值的枚举,但也可以为每个值返回一个显示友好的字符串。我以为我可以定义一个dict映射值到字符串,然后实现__str__
和一个带有字符串参数的静态构造函数,但是这有问题......
(在不同的情况下,我可能只是将此Enum的基础数据类型设为字符串而不是整数,但这被用作枚举数据库表的映射,因此整数值和字符串都是有意义的,前者是主键。)
from enum import Enum
class Fingers(Enum):
THUMB = 1
INDEX = 2
MIDDLE = 3
RING = 4
PINKY = 5
_display_strings = {
THUMB: "thumb",
INDEX: "index",
MIDDLE: "middle",
RING: "ring",
PINKY: "pinky"
}
def __str__(self):
return self._display_strings[self.value]
@classmethod
def from_string(cls, str1):
for val, str2 in cls._display_strings.items():
if str1 == str2:
return cls(val)
raise ValueError(cls.__name__ + ' has no value matching "' + str1 + '"')
转换为字符串时,出现以下错误:
>>> str(Fingers.RING)
Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
str(Fingers.RING)
File "D:/src/Hacks/PythonEnums/fingers1.py", line 19, in __str__
return self._display_strings[self.value]
TypeError: 'Fingers' object is not subscriptable
似乎问题是Enum将使用所有类变量作为枚举值,这会导致它们返回Enum类型的对象,而不是它们的基础类型。
我能想到的一些解决方法包括:
Fingers._display_strings.value
。 (但是Fingers.__display_strings
成为有效的枚举值!)if
和__str__
函数中复制dict(可能还会将其分解为一系列from_string
语句)。_get_display_strings
来返回dict,因此它不会成为枚举值。请注意,上面的初始代码和变通方法1.
使用基础整数值作为dict键。其他选项都要求dict(或if
测试)在类本身以外的地方定义,因此必须使用类名限定这些值。因此,您只能使用Fingers.THUMB
来获取枚举对象,或Fingers.THUMB.value
来获取基础整数值,但不仅仅是THUMB
。如果使用基础整数值作为dict键,那么您还必须使用它来查找dict,使用例如[Fingers.THUMB.value]
而不仅仅是[Fingers.THUMB]
对其进行索引。
所以,问题是,为Enum实现字符串映射的最佳或最Pythonic方法是什么,同时保留基础整数值?
答案 0 :(得分:12)
这可以使用stdlib Enum
完成,但aenum
1 更容易:
from aenum import Enum
class Fingers(Enum):
_init_ = 'value string'
THUMB = 1, 'two thumbs'
INDEX = 2, 'offset location'
MIDDLE = 3, 'average is not median'
RING = 4, 'round or finger'
PINKY = 5, 'wee wee wee'
def __str__(self):
return self.string
如果您希望能够通过字符串值进行查找,那么请在stdlib中实现新的类方法_missing_value_
(仅_missing_
):
from aenum import Enum
class Fingers(Enum):
_init_ = 'value string'
THUMB = 1, 'two thumbs'
INDEX = 2, 'offset location'
MIDDLE = 3, 'average is not median'
RING = 4, 'round or finger'
PINKY = 5, 'wee wee wee'
def __str__(self):
return self.string
@classmethod
def _missing_value_(cls, value):
for member in cls:
if member.string == value:
return member
1 披露:我是Python stdlib Enum
,enum34
backport和Advanced Enumeration (aenum
)图书馆的作者。
答案 1 :(得分:3)
也许我在这里忽略了这一点,但如果你定义
class Fingers(Enum):
THUMB = 1
INDEX = 2
MIDDLE = 3
RING = 4
PINKY = 5
然后在Python 3.6中你可以做到
print (Fingers.THUMB.name.lower())
我认为这就是你想要的。
答案 2 :(得分:1)
我提出的另一个解决方案是,因为整数和字符串都有意义,所以要使Enum值为(int, str)
元组,如下所示。
from enum import Enum
class Fingers(Enum):
THUMB = (1, 'thumb')
INDEX = (2, 'index')
MIDDLE = (3, 'middle')
RING = (4, 'ring')
PINKY = (5, 'pinky')
def __str__(self):
return self.value[1]
@classmethod
def from_string(cls, s):
for finger in cls:
if finger.value[1] == s:
return finger
raise ValueError(cls.__name__ + ' has no value matching "' + s + '"')
但是,这意味着Fingers
对象的repr将显示元组而不仅仅是int,并且必须使用完整的元组来创建Fingers
个对象,而不仅仅是int。即您可以f = Fingers((1, 'thumb'))
,但不能f = Fingers(1)
。
>>> Fingers.THUMB
<Fingers.THUMB: (1, 'thumb')>
>>> Fingers((1,'thumb'))
<Fingers.THUMB: (1, 'thumb')>
>>> Fingers(1)
Traceback (most recent call last):
File "<pyshell#25>", line 1, in <module>
Fingers(1)
File "C:\Python\Python35\lib\enum.py", line 241, in __call__
return cls.__new__(cls, value)
File "C:\Python\Python35\lib\enum.py", line 476, in __new__
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 1 is not a valid Fingers
更复杂的解决方法是将Enum
的元类子类化为实现自定义__call__
。 (至少覆盖__repr__
要简单得多!)
from enum import Enum, EnumMeta
class IntStrTupleEnumMeta(EnumMeta):
def __call__(cls, value, names=None, *args, **kwargs):
if names is None and isinstance(value, int):
for e in cls:
if e.value[0] == value:
return e
return super().__call__(value, names, **kwargs)
class IntStrTupleEnum(Enum, metaclass=IntStrTupleEnumMeta):
pass
class Fingers(IntStrTupleEnum):
THUMB = (1, 'thumb')
INDEX = (2, 'index')
MIDDLE = (3, 'middle')
RING = (4, 'ring')
PINKY = (5, 'pinky')
def __str__(self):
return self.value[1]
@classmethod
def from_string(cls, s):
for finger in cls:
if finger.value[1] == s:
return finger
raise ValueError(cls.__name__ + ' has no value matching "' + s + '"')
def __repr__(self):
return '<%s.%s %s>' % (self.__class__.__name__, self.name, self.value[0])
此实现与普通int枚举之间的一个区别是具有相同整数值但不同字符串(例如INDEX = (2, 'index')
和POINTER = (2, 'pointer')
)的值不会评估为相同Finger
对象,而使用普通的int枚举,Finger.POINTER is Finger.INDEX
将评估为True
。
答案 3 :(得分:1)
python 文档有一些abstract example here
,我从中可以想出这个解决方案
我已内联添加了解释,作为注释。
# we could also do class Finger(IntEnum) it's equivalent.
class Finger(int, Enum):
def __new__(cls, value, label):
# Initialise an instance of the Finger enum class
obj = int.__new__(cls, value)
# Calling print(type(obj)) returns <enum 'Finger'>
# If we don't set the _value_ in the Enum class, an error will be raised.
obj._value_ = value
# Here we add an attribute to the finger class on the fly.
# One may want to use setattr to be more explicit; note the python docs don't do this
obj.label = label
return obj
THUMB = (1, 'thumb')
INDEX = (2, 'index')
MIDDLE = (3, 'middle')
RING = (4, 'ring')
PINKY = (5, 'pinky')
@classmethod
def from_str(cls, input_str):
for finger in cls:
if finger.label == input_str:
return finger
raise ValueError(f"{cls.__name__} has no value matching {input_str}")
让我们测试一下。
In [99]: Finger(1)
Out[99]: <Finger.THUMB: 1>
In [100]: Finger.from_str("thumb")
Out[100]: <Finger.THUMB: 1>
In [101]: Finger.THUMB
Out[101]: <Finger.THUMB: 1>
In [102]: Finger.THUMB.label
Out[102]: 'thumb'
这里的最后一个测试很重要,__str__
方法是根据继承 class Finger(int, Enum)
自动创建的。
如果改为 class Finger(str, Enum)
并且 obj = int.__new__(cls, value)
变为 obj = str.__new__(cls, value)
,上述所有检查都可以工作,但对 __str__
的调用会引发错误。
In [103]: f"Finger.THUMB"
Out[103]: '1'
答案 4 :(得分:0)
好问题但是,这意味着Fingers对象的repr将显示元组而不仅仅是int,并且必须使用完整的元组来创建Fingers对象,而不仅仅是int。即你可以做到
f = Fingers((1, 'thumb'))
但不是
f = Fingers(1)
答案 5 :(得分:0)
我遇到了同样的问题,我想显示GUI ComboBox的字符串(在PyGTK中)。 我不知道解决方案的Pythonic性(甚至是一个词?),但我使用了以下方法:
from enum import IntEnum
class Finger(IntEnum):
THUMB = 1
INDEX = 2
MIDDLE = 3
RING = 4
PINKY = 5
@classmethod
def names(cls):
return ["Thumb", "Index", "Middle", "Ring", "Pinky"]
@classmethod
def tostr(cls, value):
return cls.names()[value - cls.THUMB]
@classmethod
def toint(cls, s):
return cls.names().index(s) + cls.THUMB
从您的代码中使用它们:
>>> print(Finger.INDEX)
Finger.INDEX
>>> print(Finger.tostr(Finger.THUMB))
Thumb
>>> print(Finger.toint("Ring"))
4
答案 6 :(得分:0)
尽管这不是OP所要求的,但是当您不在乎该值是否为int时,这仍然是一个不错的选择。您可以将该值用作人类可读的字符串。
来源:https://docs.python.org/3/library/enum.html
忽略值 在许多用例中,人们并不关心枚举的实际值是多少。定义这种简单枚举的方法有几种:
使用auto的实例作为值 使用对象的实例作为值 使用描述性字符串作为值 使用元组作为值,并使用自定义 new ()将intu值替换为元组 使用这些方法中的任何一种都向用户表示这些值并不重要,并且还使人们能够添加,删除或重新排序成员,而不必重新编号其余成员。
无论选择哪种方法,都应提供一个repr()来隐藏(不重要的)值:
class NoValue(Enum):
def __repr__(self):
return '<%s.%s>' % (self.__class__.__name__, self.name)
使用描述性字符串 使用字符串作为值看起来像:
class Color(NoValue):
RED = 'stop'
GREEN = 'go'
BLUE = 'too fast!'
Color.BLUE
<Color.BLUE>
Color.BLUE.value
'too fast!'