Anakhand
Anakhand

Reputation: 2988

Enum class with type `type` as mixin

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).

Upvotes: 3

Views: 5384

Answers (1)

Ethan Furman
Ethan Furman

Reputation: 69021

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.

Upvotes: 5

Related Questions