python enums with attributes
Solution 1
Python 3.4 has a new Enum data type (which has been backported as enum34
and enhanced as aenum
1). Both enum34
and aenum
2 easily support your use case:
-
aenum
(Python 2/3)import aenum class EnumWithAttrs(aenum.AutoNumberEnum): _init_ = 'a b' GREEN = 'a', 'b' BLUE = 'c', 'd'
-
enum34
(Python 2/3) or standard libraryenum
(Python 3.4+)import enum class EnumWithAttrs(enum.Enum): def __new__(cls, *args, **kwds): value = len(cls.__members__) + 1 obj = object.__new__(cls) obj._value_ = value return obj def __init__(self, a, b): self.a = a self.b = b GREEN = 'a', 'b' BLUE = 'c', 'd'
And in use:
>>> EnumWithAttrs.BLUE
<EnumWithAttrs.BLUE: 1>
>>> EnumWithAttrs.BLUE.a
'c'
1 Disclosure: I am the author of the Python stdlib Enum
, the enum34
backport, and the Advanced Enumeration (aenum
) library.
2 aenum
also supports NamedConstants
and metaclass-based NamedTuples
.
Solution 2
For Python 3:
class Status(Enum):
READY = "ready", "I'm ready to do whatever is needed"
ERROR = "error", "Something went wrong here"
def __new__(cls, *args, **kwds):
obj = object.__new__(cls)
obj._value_ = args[0]
return obj
# ignore the first param since it's already set by __new__
def __init__(self, _: str, description: str = None):
self._description_ = description
def __str__(self):
return self.value
# this makes sure that the description is read-only
@property
def description(self):
return self._description_
And you can use it as a standard enum or factory by type:
print(Status.READY)
# ready
print(Status.READY.description)
# I'm ready to do whatever is needed
print(Status("ready")) # this does not create a new object
# ready
Solution 3
Before Python 3.4 and the addition of the excellent enum
module, a good choice would have been to use a namedtuple:
from collections import namedtuple
Item = namedtuple('abitem', ['a', 'b'])
class Items:
GREEN = Item('a', 'b')
BLUE = Item('c', 'd')
These days, any supported version of Python has enum
, so please use that module. It gives you a lot more control over how each enum value is produced.
If you give each item a tuple of values, then these are passed to the __init__
method as separate (positional) arguments, which lets you set additional attributes on the enum value:
from enum import Enum
class Items(Enum):
GREEN = ('a', 'b')
BLUE = ('c', 'd')
def __init__(self, a, b):
self.a = a
self.b = b
This produces enum entries whose value is the tuple assigned to each name, as well as two attributes a
and b
:
>>> Items.GREEN, Items.BLUE
(<Items.GREEN: ('a', 'b')>, <Items.BLUE: ('c', 'd')>)
>>> Items.BLUE.a
'c'
>>> Items.BLUE.b
'd'
>>> Items(('a', 'b'))
<Items.GREEN: ('a', 'b')>
Note that you can look up each enum value by passing in the same tuple again.
If the first item should represent the value of each enum entry, use a __new__
method to set _value_
:
from enum import Enum
class Items(Enum):
GREEN = ('a', 'b')
BLUE = ('c', 'd')
def __new__(cls, a, b):
entry = object.__new__(cls)
entry.a = entry._value_ = a # set the value, and the extra attribute
entry.b = b
return entry
def __repr__(self):
return f'<{type(self).__name__}.{self.name}: ({self.a!r}, {self.b!r})>'
I added a custom __repr__
as well, the default only includes self._value_
. Now the value of each entry is defined by the first item in the tuple, and can be used to look up the enum entry:
>>> Items.GREEN, Items.BLUE
(<Items.GREEN: ('a', 'b')>, <Items.BLUE: ('c', 'd')>)
>>> Items.BLUE.a
'c'
>>> Items.BLUE.b
'd'
>>> Items('a')
<Items.GREEN: ('a', 'b')>
See the section on __init__
vs. __new__
in the documentation for further options.
Solution 4
Here's another approach which I think is simpler than the others, but allows the most flexibility:
from collections import namedtuple
from enum import Enum
class Status(namedtuple('Status', 'name description'), Enum):
READY = 'ready', 'I am ready to do whatever is needed'
ERROR = 'error', 'Something went wrong here'
def __str__(self) -> str:
return self.name
It works as expected:
>>> str(Status.READY)
ready
>>> Status.READY
<Status.READY: Status(name='ready', description='I am ready to do whatever is needed')>
>>> Status.READY.description
'I am ready to do whatever is needed'
>>> Status.READY.value
Status(name='ready', description='I am ready to do whatever is needed')
Also you are able to retrieve the enum by name (Thanks @leoll2 for pointing this out). For example
>>> Status['READY']
<Status.READY: Status(name='ready', description='I am ready to do whatever is needed')>
You get the best of namedtuple and Enum.
Solution 5
for small enums @property might work:
class WikiCfpEntry(Enum):
'''
possible supported storage modes
'''
EVENT = "Event"
SERIES = "Series"
@property
def urlPrefix(self):
baseUrl="http://www.wikicfp.com/cfp"
if self==WikiCfpEntry.EVENT:
url= f"{baseUrl}/servlet/event.showcfp?eventid="
elif self==WikiCfpEntry.SERIES:
url= f"{baseUrl}/program?id="
return url
Artem
Updated on July 09, 2022Comments
-
Artem almost 2 years
Consider:
class Item: def __init__(self, a, b): self.a = a self.b = b class Items: GREEN = Item('a', 'b') BLUE = Item('c', 'd')
Is there a way to adapt the ideas for simple enums to this case? (see this question) Ideally, as in Java, I would like to cram it all into one class.
Java model:
enum EnumWithAttrs { GREEN("a", "b"), BLUE("c", "d"); EnumWithAttrs(String a, String b) { this.a = a; this.b = b; } private String a; private String b; /* accessors and other java noise */ }
-
Lukas Graf over 11 years@bmargulies Or some watching. In this video recording of a PyCon talk] Raymond Hettinger explains why named tuples are awesome, how they work and use cases for them. Fun with Python's Newer Tools
[11:35 - 26:00]
. The examples he demonstrates include an enum type for colors. -
Ethan Furman over 4 years@andilabs: Sounds like you asking for something like mypy.
mypy
currently supports the stdlibEnum
only. Someone could take theEnum
support frommypy
and translate that foraenum.Enum
, but I have not had time. -
Nairum over 3 yearsWhy
EnumWithAttrs.BLUE.a
returns "c" instead of "a"? I need to get the second value from tuple. -
Nairum over 3 yearsThis is more descriptive and easy to use than the accepted answer.
-
Ethan Furman over 3 years@AlexeiMarinichenko: In the example code above,
a
is the first attribute andb
is the second; so to access the second attribute ofBLUE
you would doEnumWithAttrs.BLUE.b
. -
off99555 over 3 yearsIt doesn't allow me to select the Status by name e.g.
Status('ready')
. It's needed when you want user to choose the status from a list of names, and they will type a name e.g.'ready'
-
off99555 over 3 yearsDoesn't allow me to chose an item from its name e.g. I expect
Items('a')
to returnItems.GREEN
-
Martijn Pieters over 3 years@off99555: this answer was posted long before the
enum
module was added, which is what you really want to use now. -
Martijn Pieters over 3 years@off99555: I've updated the answer to make use of
enum
now that that's a universally available option. Note that anEnum
won't directly let you map arbitrary items from the tuple of options to an enum value;Items('b')
orItems('d')
still won't work, only the enum_value_
attribute is supported in lookups. You'd have to define a class method yourself that encodes custom lookup rules, e.g.@classmethod
,def lookup(cls, value):
,for entry in cls.__members__.values():
if value in {entry.a, entry.b}: return entry
. -
Philip Couling almost 3 yearsIt'd be nice to have an explanation of why
__new__
is required in this context since so may other classes don't require it. -
Gavin S. Yancey over 2 yearsWhat does the
__new__
do in your second example? Why is it needed? -
Ethan Furman over 2 years@Gavin: it creates the enum member, and it's needed so that the
.value
attribute is1
,2
, etc., and to assign the member attributesa
andb
to the passed in values. -
Eduardo Lucio about 2 yearsWhat if we need something like
class Status(str, Enum):
? I needed to change the lineobj = object.__new__(cls)
toobj = str.__new__(cls)
, but now we have thatprint(str(Status.READY == Status.ERROR))
results in " True". Any idea? 🤔🤔🤔 -
Eduardo Lucio about 2 yearsThe
def __str__(self):
method is unnecessary. 😉 -
Ovidiu S. about 2 yearsIn my humble view, extending str and enum at the same time is wrong. However, if you still want to do it you probably need to overload
def __eq__(self, other):
as the default str implementation differs from the enum class. -
leoll2 about 2 years@off99555 You can do it with
Status["READY"]