
Here's a working example of what I'm talking about. In this example, `EnumX` is a tweak of `Enum` to that has the capability that I'm talking about, and I have also pasted the code for `EnumX` later in this email. Please note that the important thing in this example is NOT what this `ChoiceEnum` class does but the ability to create custom enums that can do custom processing of assigned value/key such as what `ChoiceEnum` does, using a documented feature of `Enum`, without having to either write and debug a complicated hacks (that might not work with future updates to `Enum`) or replace hunks of `Enum` (as my `EnumX` shown here does) in ways that might not work or might not be feature-complete with future `Enum` updates. # Example usage of enhanced Enum with _preprocess_value_ support. def _label_from_key(key): return key.replace('_', ' ').title() class _ChoiceValue(str): """Easy way to have an object with immutable string content that is identified for equality by its content with a label attribute that is not significant for equality. """ def __new__(cls, content, label): obj = str.__new__(cls, content) obj.label = label return obj def __repr__(self): content_repr = str.__repr__(self) return '%s(%r, %r)' % ( self.__class__.__name__, content_repr, self.label) def get_value_label_pair(self): return (f'{self}', self.label) class ChoiceEnum(EnumX): def _preprocess_value_(key, src): value = None label = None if src == (): pass elif isinstance(src, tuple) and src != (): value, label = src elif isinstance(str, str) and src.startswith(','): label = src[1:] else: value = src if value is None: value = key label = label or _label_from_key(key) return _ChoiceValue(value, label) def __getitem__(self, key): return (f'{self._value_}', self._value_.label).__getitem__(key) def __len__(self): return 2 @property def value(self): return self._value_.get_value_label_pair() @property def label(self): return self._value_.label class Food(ChoiceEnum): APPLE = () CHEESE = () HAMBURGER = 'BURGER' SOUFFLE = ',Soufflé' CHICKEN_MCNUGGETS = ('CHX_MCNUG', 'Chicken McNuggets') DEFAULT = 'APPLE' for food in Food: print(repr(food)) # Prints... # <Food.APPLE: _ChoiceValue("'APPLE'", 'Apple')> # <Food.CHEESE: _ChoiceValue("'CHEESE'", 'Cheese')> # <Food.HAMBURGER: _ChoiceValue("'BURGER'", 'Hamburger')> # <Food.SOUFFLE: _ChoiceValue("',Soufflé'", 'Souffle')> # <Food.CHICKEN_MCNUGGETS: _ChoiceValue("'CHX_MCNUG'", 'Chicken McNuggets')> print(f'Food.DEFAULT is Food.APPLE: {Food.DEFAULT is Food.APPLE}') # Prints... # Food.DEFAULT is Food.APPLE: True Here's the implementation of `EnumX` that is being used for the above. There are just a handful of those lines that represent changes to the implementation at https://github.com/python/cpython/blob/3.8/Lib/enum.py and I there are inline comments to draw attention to those. from enum import ( Enum, EnumMeta, auto, _is_sunder, _is_dunder, _is_descriptor, _auto_null) # Copy of EnumDict with tweaks to support _preprocess_value_ class _EnumDictX(dict): def __init__(self): super().__init__() self._member_names = [] self._last_values = [] self._ignore = [] def __setitem__(self, key, value): """Duplicate all of _EnumDict.__setitem__ in order to insert a hook """ if _is_sunder(key): if key not in ( '_order_', '_create_pseudo_member_', '_generate_next_value_', '_missing_', '_ignore_', '_preprocess_value_', # <-- ): raise ValueError('_names_ are reserved for future Enum use') if key == '_generate_next_value_': setattr(self, '_generate_next_value', value) # ==================== if key == '_preprocess_value_': setattr(self, '_preprocess_value', value) # ==================== elif key == '_ignore_': if isinstance(value, str): value = value.replace(',', ' ').split() else: value = list(value) self._ignore = value already = set(value) & set(self._member_names) if already: raise ValueError( '_ignore_ cannot specify already set names: %r' % ( already, )) elif _is_dunder(key): if key == '__order__': key = '_order_' elif key in self._member_names: # descriptor overwriting an enum? raise TypeError('Attempted to reuse key: %r' % key) elif key in self._ignore: pass elif not _is_descriptor(value): if key in self: # enum overwriting a descriptor? raise TypeError('%r already defined as: %r' % (key, self[key])) # ==================== value = self._preprocess_value(key, value) # ==================== if isinstance(value, auto): if value.value == _auto_null: value.value = self._generate_next_value( key, 1, len(self._member_names), self._last_values[:]) value = value.value self._member_names.append(key) self._last_values.append(value) dict.__setitem__(self, key, value) # Subclass of EnumMeta with tweak to support _preprocess_value_ class EnumMetaX(EnumMeta): # Copy of EnumMeta.__prepare__ with tweak to support _preprocess_value_ @classmethod def __prepare__(metacls, cls, bases): # create the namespace dict enum_dict = _EnumDictX() # inherit previous flags and _generate_next_value_ function member_type, first_enum = metacls._get_mixins_(bases) if first_enum is not None: # ==================== enum_dict['_preprocess_value_'] = getattr( first_enum, '_preprocess_value_', None) # ==================== enum_dict['_generate_next_value_'] = getattr(first_enum, '_generate_next_value_', None) return enum_dict # Subclass of Enum using EnumMetaX as metaclass and with default # implementation of _preprocess_value_. class EnumX(Enum, metaclass=EnumMetaX): def _preprocess_value_(key, value): return value