如何方便地将额外信息与 Enum 成员相关联?
How can I expediently associate extra information with Enum members?
每隔一段时间,我发现自己想写这样的东西:
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
,它的成员可以通过调用带有特定字符串的构造函数来获得,我可以获得每个[=13]的price
=] 通过访问 属性。问题是,在编写 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
class 作为 type=
关键字参数传递给 argparse.ArgumentParser.add_argument
.
有没有办法在不重复的情况下将额外的值与枚举成员相关联,同时保留从“主要”值构造成员的能力?
你可以使用 __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, value, 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.get('spam')
比 Item('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
使用标准库 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', 123
HAM = 'ham', 456
EGGS = 'eggs', 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
, the enum34
backport, and the Advanced Enumeration (aenum
) 库的作者。
每隔一段时间,我发现自己想写这样的东西:
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
,它的成员可以通过调用带有特定字符串的构造函数来获得,我可以获得每个[=13]的price
=] 通过访问 属性。问题是,在编写 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
class 作为 type=
关键字参数传递给 argparse.ArgumentParser.add_argument
.
有没有办法在不重复的情况下将额外的值与枚举成员相关联,同时保留从“主要”值构造成员的能力?
你可以使用 __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, value, 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.get('spam')
比 Item('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
使用标准库 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', 123
HAM = 'ham', 456
EGGS = 'eggs', 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
, the enum34
backport, and the Advanced Enumeration (aenum
) 库的作者。