Как я могу отслеживать значения перечисления Python внутри? - PullRequest
0 голосов
/ 29 октября 2018

Моя конечная цель на самом деле состоит в том, чтобы создать вспомогательный метод в моем классе Enum, который всегда возвращает член Enum и никогда не вызывает исключение, учитывая любое возможное значение, например

Color.from_value('red')

Если значение не является частью перечисления, вспомогательный метод вернет значение по умолчанию, скажем, Color.UNKNOWN.

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

class Color(Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'

    # this doesn't work
    _values = [item.value for item in Color]

и, как и ожидалось, не работает. Возможно, это что-то, что уже встроено в Python Enums?

Ответы [ 3 ]

0 голосов
/ 29 октября 2018

С тех пор я принял ответ Netwave , но я добавляю это как небольшое отличие от оригинала. Я хотел сохранить ответ для справки.

import enum

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @staticmethod
    def from_value(value):
      try:
        return Color(value)
      except ValueError:
        return cls.UNKNOWN

Обратите внимание, что в этом подходе я изменил @classmethod на @staticmethod, так как мне больше не нужен аргумент cls. Кроме того, я обрабатываю ValueError здесь вместо KeyError, потому что это то, что Color() повышает в случае, если значение не может быть найдено. Это также работает для многозначных / кортежных перечислений.

Что касается второго подхода:

import enum

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @staticmethod
    def from_value(value):
        return Color(value) if value in _COLORITEMS else Color.UNKNOWN

_COLORITEMS = [item.value for item in Color]

Здесь я переключился с dict на list, чтобы отслеживать значения. Вместо обработки KeyError я просто проверил, есть ли значение в списке. Я мог бы использовать набор, но поскольку это перечисление, значения гарантированно будут уникальными (если я правильно понимаю).

0 голосов
/ 29 октября 2018

Вы можете отслеживать значения внутри, но это немного хлопотно:

попытка 1

_values = [k for k in vars().keys() if not k.startswith('_')]

проблемы 1

>>> # _values is a member
>>> Color._values
<Color._huh: ['RED', 'BLUE', 'GREEN']>

попытка 2

Используйте Constant из этот ответ

_values = Constant([k for k in vars().keys() if not k.startswith('_')])

проблемы 2

Это не совсем константа, поскольку вы все еще можете добавить к списку - но это можно решить, приведя к tuple:

_values = Constant(tuple([k for k in vars().keys() if not k.startswith('_')]))

Однако это все еще не решает проблему UNKNOWN.

Решение

Используя Python 3.6 или aenum 2.0 1 , вы можете указать метод _missing_, который будет вызываться, чтобы дать вашему классу последний шанс перед повышением ValueError:

class Constant:  # use Constant(object) if in Python 2
    def __init__(self, value):
        self.value = value
    def __get__(self, *args):
        return self.value
    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.value)


class Color(Enum):

    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = 'unknown'
    _values = Constant(tuple([k for k in vars().keys() if not k.startswith('_')]))

    @classmethod
    def _missing_(cls, name):
        if name.lower() in cls._values:
            return cls(name.lower())
        else:
            return cls.UNKNOWN

или

class Color(Enum):

    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = 'unknown'

    @classmethod
    def _missing_(cls, name):
        if name == name.lower():
            # avoid infinite recursion
            return cls.UNKNOWN
        else:
            return cls(name.lower())

N.B. : _missing_ должен быть возвращен только элемент enum или None - более поздние версии Python будут выдавать TypeError, если что-либо еще будет возвращено.


1 Раскрытие информации: я являюсь автором Python stdlib Enum, enum34 backport и Advanced Enumeration ( aenum) библиотека.

0 голосов
/ 29 октября 2018

Вы можете создать метод и проверить значения в классе:

import enum

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @classmethod
    def from_value(cls, value):
      try:
        return {item.value: item for item in cls}[value]
      except KeyError:
        return cls.UNKNOWN

print(Color.from_value("hey"))
print(Color.from_value("red"))

Результаты:

Color.UNKNOWN
Color.RED

Здесь у вас есть живой пример

Если вы не хотите повторять, вы всегда можете иметь внешний кеш значений:

class Color(enum.Enum):
    RED = 'red'
    BLUE = 'blue'
    GREEN = 'green'
    UNKNOWN = "unknown"

    @classmethod
    def from_value(cls, value):
      try:
        return _COLORITEMS[value]
      except KeyError:
        return cls.UNKNOWN
_COLORITEMS = {item.value: item for item in Color}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...