The __new__
method on the your enum.Enum
type is used for creating new instances of the enum values, so the Types.Unknown
, Types.Source
, etc. singleton instances. The enum call (e.g. Types('nl')
is handled by EnumMeta.__call__
, which you could subclass.
Using name aliases fits your usecases
Overriding __call__
is perhaps overkill for this situation. Instead, you can easily use name aliases:
class Types(enum.Enum):
Unknown = 0
Source = 1
src = 1
NetList = 2
nl = 2
Here Types.nl
is an alias and will return the same object as Types.Netlist
. You then access members by names (using Types[..]
index access); so Types['nl']
works and returns Types.Netlist
.
Your assertion that it won't be possible to iterate the enum's values alias free is incorrect. Iteration explicitly doesn't include aliases:
Iterating over the members of an enum does not provide the aliases
Aliases are part of the Enum.__members__
ordered dictionary, if you still need access to these.
A demo:
>>> import enum
>>> class Types(enum.Enum):
... Unknown = 0
... Source = 1
... src = 1
... NetList = 2
... nl = 2
... def __str__(self):
... if self is Types.Unknown: return '??'
... if self is Types.Source: return 'src'
... if self is Types.Netlist: return 'nl'
...
>>> list(Types)
[<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>]
>>> list(Types.__members__)
['Unknown', 'Source', 'src', 'NetList', 'nl']
>>> Types.Source
<Types.Source: 1>
>>> str(Types.Source)
'src'
>>> Types.src
<Types.Source: 1>
>>> str(Types.src)
'src'
>>> Types['src']
<Types.Source: 1>
>>> Types.Source is Types.src
True
The only thing missing here is translating unknown schemas to Types.Unknown
; I'd use exception handling for that:
try:
scheme = Types[scheme]
except KeyError:
scheme = Types.Unknown
Overriding __call__
If you want to treat your strings as values, and use calling instead of item access, this is how you override the __call__
method of the metaclass:
class TypesEnumMeta(enum.EnumMeta):
def __call__(cls, value, *args, **kw):
if isinstance(value, str):
# map strings to enum values, defaults to Unknown
value = {'nl': 2, 'src': 1}.get(value, 0)
return super().__call__(value, *args, **kw)
class Types(enum.Enum, metaclass=TypesEnumMeta):
Unknown = 0
Source = 1
NetList = 2
Demo:
>>> class TypesEnumMeta(enum.EnumMeta):
... def __call__(cls, value, *args, **kw):
... if isinstance(value, str):
... value = {'nl': 2, 'src': 1}.get(value, 0)
... return super().__call__(value, *args, **kw)
...
>>> class Types(enum.Enum, metaclass=TypesEnumMeta):
... Unknown = 0
... Source = 1
... NetList = 2
...
>>> Types('nl')
<Types.NetList: 2>
>>> Types('?????')
<Types.Unknown: 0>
Note that we translate the string value to integers here and leave the rest to the original Enum logic.
Fully supporting value aliases
So, enum.Enum
supports name aliases, you appear to want value aliases. Overriding __call__
can offer a facsimile, but we can do better than than still by putting the definition of the value aliases into the enum class itself. What if specifying duplicate names gave you value aliases, for example?
You'll have to provide a subclass of the enum._EnumDict
too as it is that class that prevents names from being re-used. We'll assume that the first enum value is a default:
class ValueAliasEnumDict(enum._EnumDict):
def __init__(self):
super().__init__()
self._value_aliases = {}
def __setitem__(self, key, value):
if key in self:
# register a value alias
self._value_aliases[value] = self[key]
else:
super().__setitem__(key, value)
class ValueAliasEnumMeta(enum.EnumMeta):
@classmethod
def __prepare__(metacls, cls, bases):
return ValueAliasEnumDict()
def __new__(metacls, cls, bases, classdict):
enum_class = super().__new__(metacls, cls, bases, classdict)
enum_class._value_aliases_ = classdict._value_aliases
return enum_class
def __call__(cls, value, *args, **kw):
if value not in cls. _value2member_map_:
value = cls._value_aliases_.get(value, next(iter(Types)).value)
return super().__call__(value, *args, **kw)
This then lets you define aliases and a default in the enum class:
class Types(enum.Enum, metaclass=ValueAliasEnumMeta):
Unknown = 0
Source = 1
Source = 'src'
NetList = 2
NetList = 'nl'
Demo:
>>> class Types(enum.Enum, metaclass=ValueAliasEnumMeta):
... Unknown = 0
... Source = 1
... Source = 'src'
... NetList = 2
... NetList = 'nl'
...
>>> Types.Source
<Types.Source: 1>
>>> Types('src')
<Types.Source: 1>
>>> Types('?????')
<Types.Unknown: 0>
__new__
method is not used forTypes(0)
orTypes('nl')
. It is used to create theTypes.Source
andTypes.Unknown
value objects instead. – PhidiasEnumMeta
(and Martjin has a very good example of doing so), the simpler, and intended, way to add this functionality would be by using aclassmethod
(as my updated answer shows). – Butane__members__
. And any time you have aliases, whether the values arestr
s,int
s, or whatever, there will not be a 1-to-1 mapping of names to values. I suggest you read all the Enum docs. – Butanelist(Types)
only lists the actual enumeration objects, not their aliases. – Phidias