Поведение Other/Default-name на python Enum

Я пытаюсь добиться следующего поведения в "перечислении" python (пока безуспешно):

Учитывая класс перечисления

class MyEnum(enum.Enum):
    A=1
    B=2
    C=3

Я хочу иметь элемент «Другой», чтобы MyEnum(5) интерпретировался как «Другой», сохраняя при этом значение 5 или,

>>> str(MyEnum(5))
... "<MyEnum.Other: 5>"

Я думал сделать что-то вроде переопределения функции _missing_, но я не знаю, как создать «пользовательский» экземпляр MyEnum, не переписывая EnumMeta.

Советы будут оценены.

EDIT: После некоторых комментариев, которые не могут точно понять мой вопрос, я не хочу иметь значение по умолчанию для перечисления, так как это значение по умолчанию не сохранит значение (которое я хочу сохранить). Я хочу только, чтобы значение было принято с именем по умолчанию.


person EZLearner    schedule 09.12.2019    source источник
comment
Это не похоже на приложение для Enum. Что именно ты пытаешься сделать?   -  person Ethan Furman    schedule 09.12.2019
comment
Почему бы нет? Я пытаюсь упростить использование перечисляемого набора констант в Интернете, и у меня есть назначение по умолчанию с неизвестным значением. Это позволяет различать известные и неизвестные данные простым и последовательным образом по всему коду, а также позволяет более инкапсулированную обработку неизвестных данных (скажем, в плагине или внешнем модуле).   -  person EZLearner    schedule 10.12.2019
comment
Ознакомьтесь с когда и где использовать Python Enum. Можно использовать значение по умолчанию, как показано в моем ответе здесь, но неверный/неизвестный код не сохраняется.   -  person Ethan Furman    schedule 10.12.2019
comment
То, что вы написали, можно было бы выполнить, просто переопределив метод _missing_ sunder в Python 3.4+, но, тем не менее, это хороший ответ.   -  person EZLearner    schedule 10.12.2019
comment
Отвечает ли это на ваш вопрос? Есть ли способ указать значение по умолчанию значение для перечислений Python?   -  person mkrieger1    schedule 10.12.2019
comment
Нет, это именно то, к чему меня отослал @EthanFurman. В любом случае, я ответил на свой вопрос, спасибо.   -  person EZLearner    schedule 10.12.2019


Ответы (2)


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

class DefaultNameEnum(Enum):
    """Support for "Other"/default-name values"""

    @classmethod
    def _missing_(cls, value):
        possible_member = cls._value2member_map_.get(value, None)

        if possible_member is None:
            possible_member = cls._create_pseudo_member_(value)

        return possible_member

    @classmethod
    def _create_pseudo_member_(cls, value):
        """
        Create a default-name member.
        """

        default_member = cls._value2member_map_.get(None, None)

        if default_member is None:
            raise ValueError("%r is not a valid %s" % (value, cls.__name__))

        # construct a singleton enum pseudo-member
        other_member = object.__new__(cls)
        other_member._name_ = default_member._name_
        other_member._value_ = value

        # use setdefault in case another thread already created a composite
        # with this value
        other_member = cls._value2member_map_.setdefault(value, other_member)

        return other_member

    def __eq__(self, other):
        """Overrides the default implementation"""
        if isinstance(other, DefaultNameEnum):
            return self._name_ == other._name_
        return False

    def __ne__(self, other):
        return not self == other

Это основано на подклассе перечисления Flag. Его использование на самом деле довольно простое — просто определите как None любое имя, которое вы хотите использовать по умолчанию. Лучше всего это проиллюстрировать на примере — рассмотрим класс:

class ABC(DefaultNameEnum):
    A = 1
    B = 2
    C = 3

    Other = None

Затем следующие консольные вызовы дадут:

>>> print([repr(mem) for mem in ABC])    
... ['<ABC.A: 1>', '<ABC.B: 2>', '<ABC.C: 3>', '<ABC.Other: None>']
>>> ABC(123)
... '<ABC.Other: 123>'
>>> ABC(1) == ABC(2)
... False
>>> ABC(123) == ABC.Other
... True
>>> ABC(123) == ABC(1374)
... True

Если вы хотите взять эту реализацию и использовать ее, обратите внимание на следующие моменты:

  1. Поведение в последней строке может быть желательным, а может и нет - в зависимости от вашего использования. Если это нежелательное использование, просто измените метод __eq__, чтобы сравнивать имена, когда self._value_ или other._value_ являются None.

  2. Если вы используете этот класс, для удобства представления вы можете захотеть, чтобы значение по умолчанию __repr__ выводило '<ABC.Other>', а не '<ABC.Other: None>' в случае значения None. Этого легко добиться, переопределив метод __repr__.

  3. Если вы не определите член по умолчанию, класс вызовет исключение при вызове его для неизвестного значения (как и любой подкласс Enum).

Я также хочу отметить, что в приведенной выше реализации я бы предпочел использовать элемент-разделитель, такой как _default_name_ или _default_value_member_, а не назначать None, но, увы, модуль enum не позволяет определять новый элемент-разделитель для Enum подклассов.

person EZLearner    schedule 10.12.2019
comment
Слава! Я ценю, что ваше решение не связано с EnumMeta. Однако строка документации для _create_pseudo_member_ неверна. Кроме того, я не понимаю, как бы вы использовали _default_value_? (Примечание: вы могли бы использовать одно начальное подчеркивание.) - person Ethan Furman; 10.12.2019
comment
Вы правы насчет строки документации - я просто взял за основу реализацию класса Flag этого класса (и, следовательно, последняя строка в этой функции, которая, я думаю, решает ошибку, которую я не понимаю). Я изменил строку документации. Спасибо! - person EZLearner; 10.12.2019
comment
Что касается _default_value_, мне следовало использовать другое имя, поэтому я переименовал его в ответе, чтобы, возможно, лучше уточнить. На ум приходят две возможности дополнительных членов: либо _default_name_, который будет содержать строку, представляющую имя Другого-члена; или _default_value_member_ (опять же, не самое удачное имя), которое будет содержать значение, метка перечисления которого должна быть меткой Other. Например, в этом случае добавьте _default_value_member_=None - person EZLearner; 10.12.2019

Это похоже на то, что вы просите:

>>> class Yep(enum.IntFlag):
...  A = 1
...  B = 2
... 
>>> Yep(5)
<Yep.4|A: 5>

Таким образом, Yep(5) действителен, но не будет Yep.Other волшебного члена.

person ForceBru    schedule 09.12.2019
comment
Я думал о чем-то в этом роде (хотя я не хочу ограничивать себя целыми значениями), но тогда я теряю функции сравнения на равенство и представимости. - person EZLearner; 09.12.2019
comment
@EZLearner, сравнение на равенство работает нормально: Yep(5) == Yep(5) - person ForceBru; 09.12.2019
comment
Да, но я имел в виду проверить, похожи ли два разных Других, а здесь этого не происходит. - person EZLearner; 10.12.2019