Inherit @unique from enum base class?

from enum import unique, IntEnum

@unique
class UniqueIntEnum(IntEnum):
    pass

class Mistake(UniqueIntEnum):
    ONE = 1
    TWO = 2
    THREE = 3
    FOUR = 3

print(Mistake.FOUR)

To my surprise, UniqueIntEnum does not enforce it on subclasses. It does work when I decortate Mistake with @unique directly but I want to make one UniqueIntEnum base and then inherit the rest from it, is that possible?

This is just because of how decorators work, isn’t it?

UniqueIntEnum = unique(IntEnum)

class Mistake(UniqueIntEnum):
    ONE = 1
    TWO = 2
    THREE = 3
    FOUR = 3

That plus the fact unique doesn’t create a subclass that messes arround with __new__ or __init__ to do its uniqueness checks on new instances.

As I understand it, all the metaclass machinery is called on instantiation. Without some custom override or hook that is called when subclassing, I can’t think how to create a subclass that always calls unique on its own subclasses.

A factory pattern solves this far more elegantly than inheritance.

1 Like

What does factory pattern mean here?

Enumerated types are … weird. They are implemented in terms of classes, using a special metaclass, but they are decidedly not like other classes. In particular, you can create an enumerated type by inheriting from Enum, IntEnum, etc, but you aren’t really expected to create “subenums” by inheriting such enumerated types.

That said, the unique decorator does not add a uniqueness property that is checked for all subclasses; it just verifies that the immediate class has the uniqueness property. The resulting class is not “marked” as needing to be unique. Here’s the definition:

def unique(enumeration):
    """
    Class decorator for enumerations ensuring unique member values.
    """
    duplicates = []
    for name, member in enumeration.__members__.items():
        if name != member.name:
            duplicates.append((name, member.name))
    if duplicates:
        alias_details = ', '.join(
                ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
        raise ValueError('duplicate values found in %r: %s' %
                (enumeration, alias_details))
    return enumeration

Notice that it does not modify enumeration in anyway; it just raises an exception if any duplicates in the enumeration are found, and returns the original enumeration as is otherwise.

2 Likes

What does factory pattern mean here?

Whenever an instance of Mistake is required, a factory function must be called to get it, e.g.:

def get_mistake(*args, **kwargs):

  @unique
  class Mistake(IntEnum):
      ONE = 1
      TWO = 2
      THREE = 3
      FOUR = 3

  return Mistake(*args, **kwargs)

The disadvantage is, the warning from @unique won’t trigger until get_mistake is called. But in Python you can do anything, pass in Mixins and all sorts of customisations (in this case that may not play nicely with Enums). Or return the class instead of an instance.

Using enum’s functional API is probably a better idea.

>>> # functional syntax 
>>> Color = Enum('Color', [('RED', 1), ('GREEN', 2), ('BLUE', 3)])
1 Like

Okay that makes sense. Thank you. Although this doesn’t work for me, since I need a unique enforcing base class that can then be used create various enums.

You can probably create a custom __init_subclass__ method that calls unique on the subclass. I think this gets called at a correct time.

I tried to do something with this but I cannot get it working:

from enum import IntEnum
from typing import Any

class UniqueEnum(IntEnum):
    def __init_subclass__(cls, **kwargs: Any) -> None:
        values = [member.value for member in cls]
        print(f"Current values for {cls}: {values}")
        if len(values) != len(set(values)):
            raise ValueError

class Mistake(UniqueEnum):
    ONE = 1
    TWO = 2
    THREE = 3
    FOUR = 3

print(Mistake.ONE)

The output I get is:

Current values for <enum 'Mistake'>: []
Mistake.ONE

Just call unique(cls), you don’t need to reimplement that logic.

that worked! but only on 3.11+?

❯ uv run -p 3.12 .\test.py
# [...] trimmed exception
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

❯ uv run -p 3.11 .\test.py
# [...] trimmed exception
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

❯ uv run -p 3.10 .\test.py
sys.version_info(major=3, minor=10, micro=15, releaselevel='final', serial=0)
Mistake.ONE

❯ uv run -p 3.9 .\test.py
sys.version_info(major=3, minor=9, micro=20, releaselevel='final', serial=0)
Mistake.ONE

@unique is documented in 3.9 enum — Support for enumerations — Python 3.9.20 documentation so I’m unsure as to why it only works in 3.11+

Forgot to post this but here’s the current working code (well working on 3.11+):

import sys
from enum import IntEnum, unique
from typing import Any


class UniqueEnum(IntEnum):
    def __init_subclass__(cls, **kwargs: Any) -> None:
        unique(cls)


class Mistake(UniqueEnum):
    ONE = 1
    TWO = 2
    THREE = 3
    FOUR = 3

print(sys.version_info)
print(Mistake.ONE)

looks like it’s because __init_subclass__ was only added in 3.11. Is there any solution left for me to support unique in 3.9?

It was added before, but apparently enum didn’t support it before. Although the changelog doesn’t quite match this, it might be worth investigating it a bit more.

I think the answer is “nothing easy”. IMO, dropping support for python 3.9 & 3.10 isn’t that bad of an option.

I think I agree, I don’t wanna complicate this too much.

Thanks to everyone who chimed in!

A bit later I found this in the official docs! https://docs.python.org/3.14/howto/enum.html#duplicatefreeenum
This works for 3.9 and lets me enforce unique values.

1 Like