Есть ли способ обеспечить количество членов, которое разрешено иметь перечислению?

Создание перечисления, состоящего ровно из n элементов, является тривиальной задачей, если я определил его сам:

class Compass(enum.Enum):
    NORTH = enum.auto()
    EAST = enum.auto()
    SOUTH = enum.auto()
    WEST = enum.auto()

## or ##

Coin = enum.Enum('Coin', 'HEADS TAILS')

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

Вот мое желаемое поведение:

class Threenum(enum.Enum):
    """An enum with exactly 3 members, a 'Holy Enum of Antioch' if you will.

    First shalt thou inherit from it. Then shalt though define members three,
    no more, no less. Three shall be the number thou shalt define, and the
    number of the members shall be three. Four shalt thou not define, neither
    define thou two, excepting that thou then proceed to three. Five is right
    out. Once member three, being the third member, be defined, then employest
    thou thy Threenum of Antioch towards thy problem, which, being intractible
    in My sight, shall be solved.
    """
    ...

class Triumvirate(Threenum):  # success
    CEASAR = enum.auto()
    POMPEY = enum.auto()
    CRASSUS = enum.auto()

class TeenageMutantNinjaTurtles(Threenum):  # TypeError
    LEONARDO = 'blue'
    DONATELLO = 'purple'
    RAPHAEL = 'red'
    MICHELANGELO = 'orange'

Trinity = Threenum('Trinity', 'FATHER SON SPIRIT')  # success

Schwartz = Threenum('Schwartz', 'UPSIDE DOWNSIDE')  # TypeError

Переопределение _generate_next_value_() позволяет принудительно использовать максимальное, но не минимальное количество участников.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
0
61
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это работа для метаклассов. Модуль enum предоставляет метакласс, который вы можете расширить, добавив желаемое поведение. Самый простой способ — определить n в метаклассе.

class ThreenumMeta(enum.EnumMeta):
    """Meta class for making enums with exactly 3 members."""
    n = 3

    def __new__(meta, *args, **kwargs):
        cls = super().__new__(meta, *args, **kwargs)
        if len(cls) not in [0, meta.n]:
            raise TypeError(f'{cls} must have exactly {meta.n} members.')
        return cls

class Threenum(enum.Enum, metaclass=ThreenumMeta):
    """An enum with exactly 3 members."""
    ...

Для этого вам понадобится новый метакласс для каждого значения n. Если вам нужно сделать это с несколькими значениями n или если n неизвестно и должно быть динамическим, тогда вам нужно что-то более гибкое, один метакласс, который позволит вам указать значение n.

class EnumMeta_NMany(enum.EnumMeta):
    """Meta class for making enums with exactly n many members, specified by kwarg."""

    def __new__(meta, *args, n=None, **kwargs):
        cls = super().__new__(meta, *args, **kwargs)
        cls.n = getattr(cls, 'n', None) or n
        if cls.n is None:
            raise TypeError(f'{cls} must specify required number of members with `n` kwarg.')
        if len(cls) not in [0, cls.n]:
            raise TypeError(f'{cls} must have exactly {cls.n} members.')
        return cls

class TwoEnum(enum.Enum, metaclass=EnumMeta_NMany, n=2):
    """An enum with exactly 2 members."""
    ...

class Threenum(enum.Enum, metaclass=EnumMeta_NMany, n=3):
    """An enum with exactly 3 members."""
    ...

Хороший вопрос/ответ.

Ethan Furman 06.08.2024 01:55

@EthanFurman Спасибо. Я думаю, что это первый раз, когда у меня возник вопрос, на который еще не было ответа по SO. А поскольку решение оказалось значительно сложнее, чем я предполагал, и на его разработку и полное понимание у меня ушло несколько часов, я подумал, что им стоит поделиться здесь.

ibonyun 06.08.2024 19:19

@EthanFurman Черт возьми, ты действительно разработал модуль перечисления ?! Очень круто. ( ̄^ ̄ )ゞ Думаю, вы бы знали, если бы существовал способ получше. Поскольку вы не предоставили ответ, могу ли я предположить, что его не существует?

ibonyun 06.08.2024 19:30

__init_subclass__ было бы проще, но его можно использовать только на Python 3.11+. Ваш ответ метакласса является наиболее широко используемым.

Ethan Furman 07.08.2024 04:31
Ответ принят как подходящий

Более простой подход — проверить количество Enum членов подкласса в методе __init_subclass__:

class Threenum(enum.Enum):
    def __init_subclass__(cls):
        if len(cls) != 3:
            raise TypeError('Subclass of Threenum must have exactly 3 members.')

Демо здесь

Вы также можете создать такой класс с фабричной функцией:

def exact_enum(number):
    class _ExactEnum(enum.Enum):
        def __init_subclass__(cls):
            if len(cls) != number:
                raise TypeError(
                    f'This Enum subclass must have exactly {number} members.')
    return _ExactEnum

Использование:

class TeenageMutantNinjaTurtles(exact_enum(3)):  # TypeError
    LEONARDO = 'blue'
    DONATELLO = 'purple'
    RAPHAEL = 'red'
    MICHELANGELO = 'orange'

Демо здесь

Среда, которую вы используете, неисправна. Если вы запустите свой код в настоящем интерпретаторе Python, он завершится неудачей.

ibonyun 06.08.2024 19:09

Но спасибо за усилия. __init_subclass__ — отличное предложение, и оно было одним из первых, что я попробовал. Но на самом деле это не работает. Причина в том, что он запускается при создании класса, то есть до создания экземпляров членов, поэтому все перечисления будут иметь длину 0 при запуске. Вы можете увидеть это, напечатав len(cls) перед проверкой длины. И, насколько я могу судить, другого способа узнать, сколько членов определено внутри __init_subclass__, нет. Вам придется подождать, пока будут созданы экземпляры членов, а затем подсчитать их.

ibonyun 06.08.2024 19:12

@ibonyun: Ошибка __init_subclass__ была исправлена ​​в Python 3.11.

Ethan Furman 07.08.2024 04:28

@EthanFurman Ага, сейчас я использую 3.9. Спасибо за разъяснение.

ibonyun 07.08.2024 19:25

Другие вопросы по теме