2

The docs state that it is possible to make an enumeration class whose members are exactly of a certain type by adding that type as a mixin to the enum class:

While :class:IntEnum is part of the :mod:enum module, it would be very simple to implement independently::

class IntEnum(int, Enum):
    pass

This demonstrates how similar derived enumerations can be defined; for example a :class:StrEnum that mixes in :class:str instead of :class:int.

Some rules:

  1. When subclassing :class:Enum, mix-in types must appear before :class:Enum itself in the sequence of bases, as in the :class:IntEnum example above.
  2. While :class:Enum can have members of any type, once you mix in an additional type, all the members must have values of that type, e.g. :class:int above. This restriction does not apply to mix-ins which only add methods and don't specify another type.
  3. When another data type is mixed in, the :attr:value attribute is not the same as the enum member itself, although it is equivalent and will compare equal.
  4. %-style formatting: %s and %r call the :class:Enum class's :meth:__str__ and :meth:__repr__ respectively; other codes (such as %i or %h for IntEnum) treat the enum member as its mixed-in type.
  5. :ref:Formatted string literals <f-strings>, :meth:str.format, and :func:format will use the mixed-in type's :meth:__format__
    unless :meth:__str__ or :meth:__format__ is overridden in the subclass, in which case the overridden methods or :class:Enum methods will be used. Use the !s and !r format codes to force usage of the :class:Enum class's :meth:__str__ and :meth:__repr__ methods.

I was trying to make an enum whose members were types (objects of type type), but since type is a special class (it is a metaclass more than a class), I'm getting a bunch of errors like argument mismatch for mro, __new__, __init__, etc:

import enum

class A: pass

class B: pass

class C(B): pass


class MyTypes(type, enum.Enum):
    SPAM = A
    EGGS = C

Example of error:

Traceback (most recent call last):
  File "<file>", line 10, in <module>
    class MyTypes(type, enum.Enum):
  File "<file>", line 182, in __new__
    dynamic_attributes = {k for c in enum_class.mro()
TypeError: descriptor 'mro' of 'type' object needs an argument

Is there a way to achieve this?


I realise this might be an XY problem, so I'll explain my concrete example. In a card game I'm developing, I have a Card class hierarchy; each of the subclasses represents a different card type. However, the mapping between card types in the game and subclasses of Card might not be 1-to-1; for example there might be some intermediate abstract classes that serve as an implementation help but that do not represent an actual card type.

So I want to make an enumeration that contains all of the game card types, and I want the members to be the actual concrete subclasses implementing those types, so that I can easily interoperate enum members and the actual classes: the idea is to access class attributes, instantiate them, etc without having to use .value every time (as in some cases I might have to work with something that is either a member of the enum or the class itself).

Anakhand
  • 2,838
  • 1
  • 22
  • 50
  • 1
    Even if this turns out to be possible, the semantics of the resulting enum members would be far too confusing. A different design would be a better idea. – user2357112 Dec 04 '20 at 08:55
  • 1
    In your example, you reuse the key `EGGS`, this lead to an error. – Dorian Turba Dec 04 '20 at 08:59
  • 1
    @Anakhand: Those work the other way around. You can have `FOO=1` and `BAR=1`, but not `FOO=1` and `FOO=2`. – user2357112 Dec 04 '20 at 09:50
  • Aliases is another name for the same value, not the same name for two values – Dorian Turba Dec 04 '20 at 09:51
  • Ah you are totally correct, for some reason I had that flipped in my mind. I've corrected the example. In any case, that's not really relevant to the question. – Anakhand Dec 04 '20 at 09:57

1 Answers1

4

Short answer: no, it is not currently possible to mix in type to an enum.


The purpose behind mixing in data types is two-fold:

  • ensure that all members are of that type
class Number(int, Enum):
    ONE = 1
    TWO = 2
    THREE = 'three'
# ValueError: invalid literal for int() with base 10: 'three'
  • make the members directly usable as that type
class Number(int, Enum):
    ONE = 1
    TWO = 2
Number.ONE + 2
# 3

As you noted, type is not a data type -- it is a metaclass. It would need special support to work. What is your use-case?


Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • I realise this might be an XY problem, so I have explained my concrete use case in an edit to my question. – Anakhand Dec 04 '20 at 15:24