Is it possible to override __new__ in an enum to parse strings to an instance?
enums, python, python-3.4
Solution
Yes, you can override the `__new__()` method of an `enum` subclass to implement a parse method if you're careful, but in order to avoid specifying the integer encoding in two places, you'll need to define the method separately, after the class, so you can reference the symbolic names defined by the enumeration.
Here's what I mean:
import enum
class Types(enum.Enum):
Unknown = 0
Source = 1
NetList = 2
def __str__(self):
if (self == Types.Unknown): return "??"
elif (self == Types.Source): return "src"
elif (self == Types.NetList): return "nl"
else: raise TypeError(self)
def _Types_parser(cls, value):
if not isinstance(value, str):
# forward call to Types' superclass (enum.Enum)
return super(Types, cls).__new__(cls, value)
else:
# map strings to enum values, default to Unknown
return { 'nl': Types.NetList,
'ntl': Types.NetList, # alias
'src': Types.Source,}.get(value, Types.Unknown)
setattr(Types, '__new__', _Types_parser)
if __name__ == '__main__':
print("Types('nl') ->", Types('nl')) # Types('nl') -> nl
print("Types('ntl') ->", Types('ntl')) # Types('ntl') -> nl
print("Types('wtf') ->", Types('wtf')) # Types('wtf') -> ??
print("Types(1) ->", Types(1)) # Types(1) -> src
Update
Here's a more table-driven version that eliminates some of the repetitious coding that would otherwise be involved:
from collections import OrderedDict
import enum
class Types(enum.Enum):
Unknown = 0
Source = 1
NetList = 2
__str__ = lambda self: Types._value_to_str.get(self)
# Define after Types class.
Types.__new__ = lambda cls, value: (cls._str_to_value.get(value, Types.Unknown)
if isinstance(value, str) else
super(Types, cls).__new__(cls, value))
# Define look-up table and its inverse.
Types._str_to_value = OrderedDict((( '??', Types.Unknown),
('src', Types.Source),
('ntl', Types.NetList), # alias
( 'nl', Types.NetList),))
Types._value_to_str = {val: key for key, val in Types._str_to_value.items()}
if __name__ == '__main__':
print("Types('nl') ->", Types('nl')) # Types('nl') -> nl
print("Types('ntl') ->", Types('ntl')) # Types('ntl') -> nl
print("Types('wtf') ->", Types('wtf')) # Types('wtf') -> ??
print("Types(1) ->", Types(1)) # Types(1) -> src
print(list(Types)) # -> [<Types.Unknown: 0>, <Types.Source: 1>, <Types.NetList: 2>]
import pickle # Demostrate picklability
print(pickle.loads(pickle.dumps(Types.NetList)) == Types.NetList) # -> True
Note that in Python 3.7+ regular dictionaries are ordered, so the use of `OrderedDict` in the code above would not be needed and it could be simplified to just:
# Define look-up table and its inverse.
Types._str_to_value = {'??': Types.Unknown,
'src': Types.Source,
'ntl': Types.NetList, # alias
'nl': Types.NetList}
Types._value_to_str = {val: key for key, val in Types._str_to_value.items()}
Problem
I want to parse strings into python enums. Normally one would implement a parse method to do so. A few days ago I spotted the __new__ method which is capable of returning different instances based on a given parameter. Here my code, which will not work: ``` import enum class Types(enum.Enum): Unknown = 0 Source = 1 NetList = 2 def __new__(cls, value): if (value == "src"): return Types.Source # elif (value == "nl"): return Types.NetList # else: raise Exception() def __str__(self): if (self == Types.Unknown): return "??" elif (self == Types.Source): return "src" elif (self == Types.NetList): return "nl" ``` When I execute my Python script, I get this message: ``` [...] class Types(enum.Enum): File "C:\Program Files\Python\Python 3.4.0\lib\enum.py", line 154, in __new__ enum_member._value_ = member_type(*args) TypeError: object() takes no parameters ``` How can I return a proper instance of a enum value? Edit 1: This Enum is used in URI parsing, in particular for parsing the schema. So my URI would look like this ``` nl:PoC.common.config <schema>:<namespace>[.<subnamespace>*].entity ``` So after a simple string.split operation I would pass the first part of the URI to the enum creation. ``` type = Types(splitList[0]) ``` type should now contain a value of the enum Types with 3 possible values (Unknown, Source, NetList) If I would allow aliases in the enum's member list, it won't be possible to iterate the enum's values alias free.