Making an enum with exactly n many members is trivial if I’ve defined it myself:
class Compass(enum.Enum):
NORTH = enum.auto()
EAST = enum.auto()
SOUTH = enum.auto()
WEST = enum.auto()
## or ##
Coin = enum.Enum('Coin', 'HEADS TAILS')
But what if this enum will be released into the wild to be subclassed by other users? Let’s assume that some of its extra behaviour depends on having the right number of members so we need to enforce that users define them correctly.
Here’s my desired behaviour:
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
YinYang = Threenum('Schwartz', 'UPSIDE DOWNSIDE') # TypeError
Overriding _generate_next_value_()
allows the enforcement of a maximum number of members, but not a minimum.
This is a job for metaclasses. The enum
module provides a metaclass which you can extend with your desired behaviour. The simplest way is to define n in a metaclass.
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."""
...
Doing this, you end up with a metaclass for each n-membered enum. If you need to do this with multiple values of n, or if n is unknown and needs to be dynamic, then you need something more flexible, a metaclass that will allow you to specific the value of n.
class EnumMeta_NMany(enum.EnumMeta):
"""Meta class for making enums with exactly n many members. n specifed by kwarg."""
def __new__(meta, name, bases, classdict, n=None):
cls = super().__new__(meta, name, bases, classdict)
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."""
...