假设我有一个像这样的 Python 枚举:
from enum import Enum
class FRUIT(Enum):
APPLE = 1
BANANA = 2
LEMON = 3
ORANGE = 4
Run Code Online (Sandbox Code Playgroud)
我想以有用的方式对它们进行“子集化”,比如可以说:
if fruit in FRUIT.CITRUS:
make_juice()
Run Code Online (Sandbox Code Playgroud)
我在某个地方定义了:CITRUS = {LEMON, ORANGE}
。
我想将子集保留为主枚举的属性,因为它会在上下文中保留子集的使用。
我知道我可以做这样的事情,但我强烈希望避免使用方法调用符号。而且每次需要时重建集合似乎很浪费:
@classmethod
def CITRUS(cls):
return {cls.LEMON, cls.ORANGE}
Run Code Online (Sandbox Code Playgroud)
有没有办法在枚举元类完成其工作后添加类属性而不破坏事物?
由于它本身并不意味着是一种水果,而是一种水果,因此创建一个以水果类型作为成员的CITRUS
单独子类更有意义:Enum
class FRUIT_TYPE(Enum):
CITRUS = {FRUIT.LEMON, FRUIT.ORANGE}
Run Code Online (Sandbox Code Playgroud)
这样你就可以做类似的事情:
fruit = FRUIT.LEMON
if fruit in FRUIT_TYPE.CITRUS.value:
make_juice()
Run Code Online (Sandbox Code Playgroud)
然而,必须用来FRUIT_TYPE.CITRUS.value
检查会员资格看起来很麻烦。要允许对其FRUIT_TYPE.CITRUS
自身进行成员资格检查,您还可以创建FRUIT_TYPE
以下子类:set
class FRUIT_TYPE(set, Enum):
CITRUS = {FRUIT.LEMON, FRUIT.ORANGE}
Run Code Online (Sandbox Code Playgroud)
这样您就可以执行以下操作:
fruit = FRUIT.LEMON
if fruit in FRUIT_TYPE.CITRUS:
make_juice()
Run Code Online (Sandbox Code Playgroud)
更新:另一件需要考虑的事情,我认为我更喜欢,总的来说,是将子集成员资格定义为每个 Enum 成员的属性,例如:
fruit = FRUIT.ORANGE # ---or whatever, probably in far away code---
...
if fruit.is_citrus:
make_juice()
Run Code Online (Sandbox Code Playgroud)
这些可以被定义为@property
类上的 s 并且不会遇到下面提到的可变性问题。
class FRUIT(Enum):
APPLE = 1
BANANA = 2
LEMON = 3
ORANGE = 4
@property
def is_citrus(self):
return self in frozenset((FRUIT.LEMON, FRUIT.ORANGE))
Run Code Online (Sandbox Code Playgroud)
感谢其他受访者提供了非常有用的观点。这是我在考虑其他答案后最终所做的事情,然后是我的理由:
from enum import Enum
class FRUIT(Enum):
APPLE = 1
BANANA = 2
LEMON = 3
ORANGE = 4
FRUIT.CITRUS_TYPES = frozenset((FRUIT.LEMON, FRUIT.ORANGE))
Run Code Online (Sandbox Code Playgroud)
这工作正常并且(令我惊讶的是)不会破坏任何其他Enum
行为:
# ---CITRUS_TYPES subset has desired behavior---
>>> FRUIT.LEMON in FRUIT.CITRUS_TYPES
True
>>> FRUIT.APPLE in FRUIT.CITRUS_TYPES
False
>>> "foobar" in FRUIT.CITRUS_TYPES
False
# ---CITRUS_TYPES has not become a member of FRUIT enum---
>>> tuple(FRUIT)
(FRUIT.APPLE: 1>, <FRUIT.BANANA: 2>, <FRUIT.LEMON: 3>, <FRUIT.ORANGE: 4>)
>>> FRUIT.APPLE in FRUIT
True
>>> FRUIT.CITRUS_TYPES in FRUIT
DeprecationWarning: using non-Enums in containment checks will raise TypeError in Python 3.8
False
# ---CITRUS_TYPES not reported by dir(FRUIT)---
>>> dir(FRUIT)
['APPLE', 'BANANA', 'LEMON', 'ORANGE', '__class__', '__doc__', '__members__', '__module__']
# ---But it does appear on FRUIT.__dict__---
FRUIT.__dict__ == {
'_generate_next_value_': <function Enum._generate_next_value_ at 0x1010e9268>,
'__module__': '__main__',
'__doc__': 'An enumeration.',
'_member_names_': ['APPLE', 'BANANA', 'LEMON', 'ORANGE'],
'_member_map_': OrderedDict([
('APPLE', <FRUIT.APPLE: 1>),
('BANANA', <FRUIT.BANANA: 2>),
('LEMON', <FRUIT.LEMON: 3>),
('ORANGE', <FRUIT.ORANGE: 4>)
]),
'_member_type_': <class 'object'>,
'_value2member_map_': {
1: <FRUIT.APPLE: 1>,
2: <FRUIT.BANANA: 2>,
3: <FRUIT.LEMON: 3>,
4: <FRUIT.ORANGE: 4>,
},
'APPLE': <FRUIT.APPLE: 1>,
'BANANA': <FRUIT.BANANA: 2>,
'LEMON': <FRUIT.LEMON: 3>,
'ORANGE': <FRUIT.ORANGE: 4>,
'__new__': <function Enum.__new__ at 0x1010e91e0>,
'CITRUS_TYPES': frozenset({<FRUIT.LEMON: 3>, <FRUIT.ORANGE: 4>})
}
Run Code Online (Sandbox Code Playgroud)
所以它似乎存储CITRUS_TYPES
在类上,但隐藏它dir()
出于某种原因将其隐藏。
但它确实有一个漏洞,因为添加的属性是可变的,就像任何其他类属性一样;如果客户端代码的某些部分分配给FRUIT.CITRUS_TYPES
,FRUIT
不会抱怨,这当然会破坏事情。这种行为与Enum
,后者在尝试分配时引发AttributeError
。
我认为这可以通过使其成为一个来解决classproperty
,我最终尝试了这一点,但我早期的尝试并没有阻止突变。那里描述的更复杂的类属性实现可能会起作用,但我最终对上面的简单方法感到满意。
@blhsing 提出了一个有趣的问题,即这样的属性是否FRUIT
有意义。我理解他的观点,并且可能会采纳他的观点,但我目前的观点是,将“与水果相关”的特征本地化到单个导入名称对我来说是最好的。因此,人们可以将一组FRUIT
严格的水果类型和水果子集视为一组独特的集合。我发现这种严格性对于我当前的目的来说并没有什么回报,并且更愿意将其视为更多相关常量值的集合,包括成员和子集。当然是YMMV。就像我说的,我可能还会采纳他的观点。FRUIT