如何向 Python 枚举添加成员子集?

sca*_*nny 5 python enums

假设我有一个像这样的 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)

有没有办法在枚举元类完成其工作后添加类属性而不破坏事物?

blh*_*ing 5

由于它本身并不意味着是一种水果,而是一种水果,因此创建一个以水果类型作为成员的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)

  • “FRUIT_TYPE.CITRUS.value”看起来很糟糕。尝试添加“set”,例如“class FRUIT_TYPE(set, Enum):”——这样,“CITRUS”也是一个“set”,并且可以说“iffruit in FRUIT_TYPE.CITRUS”。 (2认同)

sca*_*nny 3

更新:另一件需要考虑的事情,我认为我更喜欢,总的来说,是将子集成员资格定义为每个 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