问题描述
每隔一段时间,我发现自己想写这样的东西:
import enum
class Item(enum.Enum):
SPAM = 'spam'
HAM = 'ham'
EGGS = 'eggs'
@property
def price(self):
if self is self.SPAM:
return 123
elif self is self.HAM:
return 456
elif self is self.EGGS:
return 789
assert False
item = Item('spam')
print(item) # Item.SPAM
print(item.price) # 123
这正是我想要的:我有一个枚举Item
,它的成员可以通过调用带有某些字符串的构造函数来获取,我可以获取每个price
的{{1}}通过访问属性。问题是在编写 Item
方法时,我必须再次枚举方法中的所有枚举成员。 (此外,使用这种技术将可变对象与成员关联起来会变得有点复杂。)
我可以这样写枚举:
price
现在我不必在方法中重复成员。问题是,这样做我失去了通过调用 import enum
class Item(enum.Enum):
SPAM = ('spam',123)
HAM = ('ham',456)
EGGS = ('eggs',789)
@property
def value(self):
return super().value[0]
@property
def price(self):
return super().value[1]
item = Item.SPAM
print(item) # Items.SPAM
print(item.value) # spam
print(item.price) # 123
获得 Item.SPAM
的能力:
Item('spam')
这对我很重要,因为我希望能够将 >>> Item('spam')
Traceback (most recent call last):
File "<stdin>",line 1,in <module>
File "/usr/lib/python3.9/enum.py",line 360,in __call__
return cls.__new__(cls,value)
File "/usr/lib/python3.9/enum.py",line 677,in __new__
raise ve_exc
ValueError: 'spam' is not a valid Item
类作为 Item
关键字参数传递给 type=
。
有没有一种方法可以将额外的值与枚举成员相关联而不重复我自己,同时保留从它们的“主要”值构造成员的能力?
解决方法
使用 stdlib Enum
,您需要创建自己的 __new__
:
import enum
class Item(enum.Enum):
#
SPAM = 'spam',123
HAM = 'ham',456
EGGS = 'eggs',789
#
def __new__(cls,value,price):
obj = object.__new__(cls)
obj._value_ = value
obj.price = price
return obj
如果这是您需要做的很多事情,您可以改用 aenum
library1。
import aenum
class Item(aenum.Enum):
#
_init_ = 'value price'
#
SPAM = 'spam',789
无论哪种方式,您都以:
>>> item = Item.SPAM
>>> print(item) # Items.SPAM
Item.SPAM
>>> print(item.value) # spam
spam
>>> print(item.price) # 123
123
>>> Item('spam')
<Item.SPAM: 'spam'>
1 披露:我是 Python stdlib Enum
、enum34
backport 和 Advanced Enumeration (aenum
) 库的作者。
你可以使用__init__
吗?
class Item(enum.Enum):
SPAM = ('spam',123)
HAM = ('ham',456)
EGGS = ('eggs',789)
def __init__(self,item_type,price):
self._type = item_type
self._price = price
@property
def value(self):
return self._type
@property
def price(self):
return self._price
In []: item = Item.SPAM
In []: item.name,item.value,item.price
Out[]: ('SPAM','spam',123)
In []: Item.SPAM
Out[]: <Item.SPAM: ('spam',123)>
Item('spam')
不会工作,因为 EnumMeta.__call__
不支持它。以下来自source from cpython 3.8 enum.py
(l.313
)
def __call__(cls,names=None,*,module=None,qualname=None,type=None,start=1):
"""
Either returns an existing member,or creates a new enum class.
This method is used both when an enum class is given a value to match
to an enumeration member (i.e. Color(3)) and for the functional API
(i.e. Color = Enum('Color',names='RED GREEN BLUE')).
When used for the functional API:
`value` will be the name of the new class.
`names` should be either a string of white-space/comma delimited names
(values will start at `start`),or an iterator/mapping of name,value pairs.
`module` should be set to the module this class is being created in;
if it is not set,an attempt to find that module will be made,but if
it fails the class will not be picklable.
`qualname` should be set to the actual location this class can be found
at in its module; by default it is set to the global scope. If this is
not correct,unpickling will fail in some circumstances.
`type`,if set,will be mixed in as the first base class.
"""
if names is None: # simple value lookup
return cls.__new__(cls,value)
# otherwise,functional API: we're creating a new Enum type
return cls._create_(
value,names,module=module,qualname=qualname,type=type,start=start,)
def __contains__(cls,member):
if not isinstance(member,Enum):
raise TypeError(
"unsupported operand type(s) for 'in': '%s' and '%s'" % (
type(member).__qualname__,cls.__class__.__qualname__))
return isinstance(member,cls) and member._name_ in cls._member_map_
...
所以我想说 Item('spam')
最好去 Item.get('spam')
,而不是 Item.SPAM
。如果查询不存在,它也会优雅地失败
...
@property
def __items(self):
return {v.value: cls.__getattr__(k) for k,v in cls.__members__.items()}
@classmethod
def get(cls,item):
return cls.__items.get(item)
...
In []: Item.get('spam')
Out[]: <Item.SPAM: ('spam',123)>
In []: Item.get('spamm') # returns None