ОБНОВЛЕНИЕ: Еще одна вещь, которую я хотел бы рассмотреть, что, мне кажется, мне нравится больше, в целом, это определение членства в подмножестве как атрибута каждого члена Enum, например:
fruit = FRUIT.ORANGE # ---or whatever, probably in far away code---
...
if fruit.is_citrus:
make_juice()
Они могут быть определены как @property
с в классе и не страдают от проблем изменчивости, упомянутых ниже.
class FRUIT(Enum):
APPLE = 1
BANANA = 2
LEMON = 3
ORANGE = 4
@property
def is_citrus(self):
return self in frozenset((FRUIT.LEMON, FRUIT.ORANGE))
Спасибо другим респондентам, которые все внесли очень полезныйточки зрения. Вот что я в конечном итоге сделал после рассмотрения других ответов, после чего я привел следующее обоснование:
from enum import Enum
class FRUIT(Enum):
APPLE = 1
BANANA = 2
LEMON = 3
ORANGE = 4
FRUIT.CITRUS_TYPES = frozenset((FRUIT.LEMON, FRUIT.ORANGE))
Это прекрасно работает и (на удивление для меня) не нарушает ни одно из других действий 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>})
}
Таким образом, он, похоже, хранит CITRUS_TYPES
в классе, но скрывает его от dir()
по любой причине.
У него есть уязвимость , хотя, вчто добавленный атрибут является изменяемым, как и любой другой атрибут класса;если какая-то часть кода клиента присваивается FRUIT.CITRUS_TYPES
, FRUIT
не будет жаловаться, и это, конечно, сломает вещи. Это поведение отличается от поведения Enum
члена, который вызывает AttributeError
при попытке присвоения.
Я думал, что это можно исправить, сделав его classproperty
, который я закончилдо попытки, но мои ранние попытки не помешали мутации. Описанная там более сложная реализация свойств класса может сработать, но я в итоге остался доволен простым подходом, описанным выше.
@ blhsing поднимает интересный вопрос о том, имеет ли смысл такой атрибут в FRUIT
. Я понимаю его точку зрения и, возможно, еще приму его точку зрения, но мое текущее мнение состоит в том, что для меня лучше всего локализовать характеристики, связанные с фруктами, для одного импортированного имени. Можно было бы считать FRUIT
строгим набором видов фруктов и подмножеств фруктов, таким образом, как отдельный набор. Я считаю эту строгость непригодной для моих текущих целей и предпочитаю думать о FRUIT
как о совокупности связанных значений констант, включая как члены, так и подмножества. YMMV конечно. Как я уже сказал, я еще могу принять его точку зрения.