Почему в Python Enums разрешены изменяемые значения?

Это своего рода продолжение Почему изменяемые значения в Python перечисляет один и тот же объект?.

Если значения Enum являются изменяемыми (например, lists и т. д.), эти значения можно изменить в любое время. Я думаю, что это создает некоторую проблему, если элементы Enum извлекаются по значению, особенно если кто-то непреднамеренно меняет значение Enum, которое он ищет:

>>> from enum import Enum
>>> class Color(Enum):
        black = [1,2]
        blue = [1,2,3]

>>> val_1 = [1,2]
>>> val_2 = [1,2,3]

>>> Color(val_1)
<Color.black: [1, 2]>

>>> Color(val_2)
<Color.blue: [1, 2, 3]>

>>> my_color = Color(val_1)
>>> my_color.value.append(3)

>>> Color(val_2)
<Color.black: [1, 2, 3]>

>>> Color(val_1)
Traceback (most recent call last):
  ...
ValueError: [1, 2] is not a valid Color

Я думаю, что с учетом обычных идиом Python это нормально, подразумевая, что пользователи могут использовать mutables в качестве своих Enum значений, но просто чтобы понять, какие черви они могут открыть .

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

Не было бы более эффективным (хотя и менее гибким) ограничить значения Enum только неизменяемыми типами, чтобы поиск по значению можно было реализовать с помощью dict?


person Billy    schedule 17.11.2016    source источник


Ответы (2)


Похоже, ответ на мой второй вопрос прятался у всех на виду в исходном коде enum.py.

Каждый Enum does содержит dict из value->member пар для хешируемых (т. е. неизменяемых) значений, и когда вы ищете Enum по значению, он пытается получить элемент из этого dict . Если значение не является хэшируемым, оно затем сравнивает на равенство все существующие значения Enum, возвращая член, если находит совпадение. Соответствующий код находится в строках 468-476 в enum.py:

try:
    if value in cls._value2member_map_:
        return cls._value2member_map_[value]
except TypeError:
    # not there, now do long search -- O(n) behavior
    for member in cls._member_map_.values():
        if member._value_ == value:
            return member
raise ValueError("%r is not a valid %s" % (value, cls.__name__))

Таким образом, создается впечатление, что разработчики enum.py хотели иметь быстрый поиск при получении Enum по значению, но при этом хотели обеспечить гибкость изменяемых значений для значений Enum (хотя я до сих пор не могу придумать причину, почему кому-то это вообще нужно).

person Billy    schedule 17.11.2016
comment
Вот пример использования изменчивости: скажем, программа имеет несколько кодов выхода, составляющих перечисление, и каждый из кодов имеет собственное выходное сообщение (например, CODE = [123, "Exit #123"]). Один из случаев выхода — когда пользователь неправильно вызывает скрипт, поэтому вам нужно показать ссылку на параметр. Проблема в том, что ссылка инкапсулирована в объект ArgumentParser. Итак, сначала вы инициализируете перечисление, затем выполняете несколько add_argument(), а затем делаете что-то вроде этого: ExitCode.HELP.value[1] += '\n' + args.format_help(). Вуаля! - person hidefromkgb; 09.11.2018
comment
@hidefromkgb Насколько я понимаю ваш пример, он будет работать с кортежем. - person Graham; 05.12.2018
comment
одним из вариантов использования может быть: ``` class Foo(Enum): NO_REPLY = {'name': 'Team Bar', 'email': '[email protected]'} SUPPORT = {'name': 'Team Bar', 'email': '[email protected]'} ``` Но я почему-то чувствую, что это нехорошо, и вместо этого делаю следующее: ``` class Foo(Enum): NO_REPLY SUPPORT ``` и затем сохраняю словарь: ``` addresss = {Foo.NO_REPLY: {'name': 'Team Bar', 'email': '[email protected]'}, Foo.SUPPORT: {'name': 'Team Bar' , 'электронная почта': '[email protected]'}} ``` - person Vikas Prasad; 06.11.2019
comment
Если подумать, я думаю, что на самом деле я могу использовать enum для моего варианта использования более чистым способом, используя namedtuple: ``` Address = namedtuple('Address', ['name', 'email']) class Foo(Enum): NO_REPLY = Address('Team Bar', '[email protected]') SUPPORT = Address('Team Bar', '[email protected]') ``` - person Vikas Prasad; 06.11.2019

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

Примечание Значения элементов Enum Значения элементов могут быть любыми: int, str и т. д. Если точное значение не имеет значения, вы можете использовать автоматические экземпляры, и для вас будет выбрано подходящее значение. Следует соблюдать осторожность, если вы смешиваете auto с другими значениями. https://docs.python.org/3/library/enum.html#creating-an-enum

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

person jxramos    schedule 11.09.2019