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.
In a word, yes. As martineau illustrates you can replace the
__new__
method after the class has been instanciated (his original code):and also as his demo code illustrates, if you are not extremely careful you will break other things such as pickling, and even basic member-by-value lookup:
Martijn showed is a clever way of enhancing
EnumMeta
to get what we want:but this puts us having duplicate code, and working against the Enum type.
The only thing lacking in basic Enum support for your use-case is the ability to have one member be the default, but even that can be handled gracefully in a normal
Enum
subclass by creating a new class method.The class that you want is:
and in action:
If you really need value aliases, even that can be handled without resorting to metaclass hacking:
which gives us:
I think the by far easiest solution to your problem is to use the functional API of the
Enum
class which gives more freedom when it comes to choosing names since we specify them as strings:This creates an enum with name aliases. Mind the order of the entries in the
names
list. The first one will be chosen as default value (and also returned forname
), further ones are considered as aliases but both can be used:To use the
name
property as a return value forstr(Types.src)
we replace the default version fromEnum
:Note that we also replace the
__format__
method which is called bystr.format()
.Yes, you can override the
__new__()
method of anenum
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:
Update
Here's a more table-driven version that helps eliminates some of the repetitious coding that would otherwise be involved:
I don't have enough rep to comment on the accepted answer, but in Python 2.7 with the enum34 package the following error occurs at run-time:
"unbound method <lambda>() must be called with instance MyEnum as first argument (got EnumMeta instance instead)"
I was able to correct this by changing:
to the following, wrapping the lambda in with staticmethod():
This code tested correctly in both Python 2.7 and 3.6.
The
__new__
method on the yourenum.Enum
type is used for creating new instances of the enum values, so theTypes.Unknown
,Types.Source
, etc. singleton instances. The enum call (e.g.Types('nl')
is handled byEnumMeta.__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:Here
Types.nl
is an alias and will return the same object asTypes.Netlist
. You then access members by names (usingTypes[..]
index access); soTypes['nl']
works and returnsTypes.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:
Aliases are part of the
Enum.__members__
ordered dictionary, if you still need access to these.A demo:
The only thing missing here is translating unknown schemas to
Types.Unknown
; I'd use exception handling for that: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:Demo:
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:This then lets you define aliases and a default in the enum class:
Demo: