Как представить enum в Python

1

Я знаю, я знаю, уже есть подобные вопросы здесь и там. Но их вопросы и ответы не совсем то, что я ищу. Кроме того, они заблокированы вопросом, поэтому я не могу добавить к ним новый ответ. SMH.

Во-первых, давайте проясним вопрос, чтобы понять его масштаб. При использовании перечисления на других статических языках, например:

public enum Size
{
    SMALL=0,
    MIDIUM=1,
    LARGE=2,
    BIG=2  // There can possibly be an alias
}

мы хотим, чтобы это помогло нам:

  1. Охрана от опечатки при ссылке на значение. Например, var foo = Size.SMALL действителен, var bar = Size.SMAL должен генерировать var bar = Size.SMAL ошибку.
  2. Значения Enum могут поддерживать строки, такие как HTTP404 = "Not Found", HTTP200 = "OK",... (Поэтому эти реализации, основанные на range(N), неприемлемы.)
  3. При определении параметра как определенного типа Enum он служит в качестве правила для принятия только таких значений. Например, public void Foo(Size size) {...}
  4. Я также хочу, чтобы ценности были первоклассными гражданами в моем решении 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.
  • 1
    Я не понимаю, чем ваш вопрос отличается от тех, которые вы связали. Почему ответы там не отвечают на ваш вопрос?
  • 0
    @ Aran-Fey Ни один из этих вопросов не прояснил требования, поэтому их ответы, как правило, основывались на разном предположении в разном ответе.
Показать ещё 9 комментариев
Теги:
enums

2 ответа

1

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 и обнаружил, что уже есть пакет с таким же именем, используя тот же подход, что и мой, и хорошо документирован. Так что это успокаивает. Я просто попробую установить вену. :-)

  • 0
    enum.Enum не заставляет вас принимать в члены функции enum. Используя Size в качестве enum.Enum enum, вы можете принять size как int и вызвать Size(size) чтобы получить член enum.
  • 0
    @ user2357112, спасибо, приятно знать, что существует такой синтаксический сахар из стандартного Enum: SIZE(value) как эквивалентный, как if value not in SIZE: raise ValueError("...") . Лично я думаю, что последний более питоничен, потому что «явное лучше, чем неявное» .
Показать ещё 2 комментария
0

Реализация с помощью 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
  • 0
    Спасибо за отрицательное голосование. Я действительно знал, что в Python 3 есть стандартный модуль Enum который, по сути, является расширенным набором для моей однострочной реализации. Однако есть один сценарий, который мне не подходит. Я хочу, чтобы ценности были первоклассным гражданином в моем списке; Я хочу, чтобы моя t_shirt_size(...) принимала реальное значение, а не только член enum. Стандартный подход перечисления НЕ позволил бы эти 2 использования: assert SIZE.MEDIUM == 1 или assert 1 in SIZE . Теперь ваш конкретный ответ не соответствует моей потребности. Тем не менее, я не собираюсь опускать это. Вы тоже были бы честны?
  • 0
    @RayLuo: assert SIZE.MEDIUM == 1 работает с IntEnum . Второй не работает как есть, но вы можете добавить свой собственный метод для выполнения тяжелой работы.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню