Я знаю, я знаю, уже есть подобные вопросы здесь и там. Но их вопросы и ответы не совсем то, что я ищу. Кроме того, они заблокированы вопросом, поэтому я не могу добавить к ним новый ответ. SMH.
Во-первых, давайте проясним вопрос, чтобы понять его масштаб. При использовании перечисления на других статических языках, например:
public enum Size
{
SMALL=0,
MIDIUM=1,
LARGE=2,
BIG=2 // There can possibly be an alias
}
мы хотим, чтобы это помогло нам:
var foo = Size.SMALL
действителен, var bar = Size.SMAL
должен генерировать var bar = Size.SMAL
ошибку.HTTP404 = "Not Found", HTTP200 = "OK",...
(Поэтому эти реализации, основанные на range(N)
, неприемлемы.)public void Foo(Size size) {...}
Я также хочу, чтобы ценности были первоклассными гражданами в моем решении Enum. Значение, мои функции def parser(value_from_the_wire):...
хотели бы потреблять некоторые собственные значения (например, целое число или строку и т.д.), А не потреблять член Enum. Это сложная часть стандартного Enum в Python 3:
assert 2 == MY_ENUM.MY_VALUE
будет работать только тогда, когда MY_ENUM был получен из IntEnum
(и нет стандартного StrEnum
хотя нетрудно подклассифицировать его самостоятельно)assert 2 in MY_ENUM
не работает, даже если MY_ENUM был получен из IntEnum
.TL; DR: использовать вену
Поэтому мое решение Python для удовлетворения 3-х критериев в вопросе основано на namedtuple, и реализация кажется более простой, чем новый встроенный Enum в Python 3.
from collections import namedtuple
def enum(name=None, **kwargs):
"""
:param name: An optional type name, which only shows up when debugging by print(...)
"""
# This actual implementation below is just a one-liner, even within 80-char
return namedtuple(name or "Const_%d" % id(kwargs), kwargs.keys())(**kwargs)
Использование теперь простое.
# definition syntax
SIZE = enum("Size", SMALL=0, MEDIUM=1, LARGE=2, BIG=2)
# usage on referencing
print(SIZE.SMALL) # got 0, instead of <SIZE.SMALL: 0>
try:
print(SIZE.SMAL) # got AttributeError
assert False, "should not arrive this line"
except AttributeError:
pass
# usage on comparison and contains-check
assert SIZE.MEDIUM == 1 # works. It won't work when using standard Enum (unless using IntEnum)
assert 1 in SIZE # works. It won't work when using standard Enum (unless you wrote it as SIZE(1)).
# usage on regulating input value
def t_shirt_size(size):
if size not in SIZE:
raise ValueError("Invalid input value")
print("Placing order with size: %s" % size)
t_shirt_size(SIZE.MEDIUM) # works
t_shirt_size(2) # also want this to work
try:
t_shirt_size(7) # got AssertionError
assert False, "This line should not be reached!"
except ValueError:
pass
EDIT 1: я действительно знал, что в Python 3 есть стандартный модуль Enum, который, по правде говоря, в значительной степени является надмножеством для моей однострочной реализации ниже. Однако есть один сценарий, который стандартный Enum мне не подойдет. Я хочу, чтобы ценности были первоклассным гражданином в моем перечислении; Я хочу, чтобы моя t_shirt_size(...)
принимала действительное значение, а не только член перечисления. Стандартный подход enum НЕ допускал эти 2 использования: assert SIZE.MEDIUM == 1
не assert 1 in SIZE
.
EDIT 2: Учитывая, что люди склонны стереотипировать эту тему как дубликат, я планировал фактически реализовать свой подход как отдельный модуль с большим количеством документации. Я даже придумал классное имя, venum
, V означает Value. Именно в это время я проверил имя в pypi и обнаружил, что уже есть пакет с таким же именем, используя тот же подход, что и мой, и хорошо документирован. Так что это успокаивает. Я просто попробую установить вену. :-)
enum.Enum
не заставляет вас принимать в члены функции enum. Используя Size
в качестве enum.Enum
enum, вы можете принять size
как int и вызвать Size(size)
чтобы получить член enum.
SIZE(value)
как эквивалентный, как if value not in SIZE: raise ValueError("...")
. Лично я думаю, что последний более питоничен, потому что «явное лучше, чем неявное» .
Реализация с помощью Python 3 Enum
:
from enum import IntEnum
class SIZE(Enum):
SMALL = 0
MEDIUM = 1
LARGE = 2
BIG = 2
@classmethod
def contains(cls, value):
return any([e.value == value for e in cls])
и используя:
print(SIZE.SMALL) # got <SIZE.SMALL: 0>
print(SIZE.SMAL) # got AttributeError
def t_shirt_size(size):
assert size in SIZE, "Invalid input value"
place_order_with_size(size)
t_shirt_size(SIZE.MEDIUM) # works
t_shirt_size(7) # got AssertionError
Enum
который, по сути, является расширенным набором для моей однострочной реализации. Однако есть один сценарий, который мне не подходит. Я хочу, чтобы ценности были первоклассным гражданином в моем списке; Я хочу, чтобы моя t_shirt_size(...)
принимала реальное значение, а не только член enum. Стандартный подход перечисления НЕ позволил бы эти 2 использования: assert SIZE.MEDIUM == 1
или assert 1 in SIZE
. Теперь ваш конкретный ответ не соответствует моей потребности. Тем не менее, я не собираюсь опускать это. Вы тоже были бы честны?
assert SIZE.MEDIUM == 1
работает с IntEnum
. Второй не работает как есть, но вы можете добавить свой собственный метод для выполнения тяжелой работы.