python enums with attributes
Question:
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 */
}
Answers:
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.
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 library enum
(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
.
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
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.
Inspired by some of the other answers, I found a way of including additional fields to an enum as ‘transparently’ as possible, overcoming some shortcomings of the other approaches. Everything works the same as if the additional fields weren’t there.
The enum is immutable just like a tuple, the value of the enum is just as it would be without the additional fields, it works just like a normal enum with auto()
, and selecting an enum by value works.
import enum
# Common base class for all enums you want to create with additional fields (you only need this once)
class EnumFI(enum.Enum):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls._values = []
def __new__(cls, *args, **kwargs):
value = args[0]
if isinstance(value, enum.auto):
if value.value == enum._auto_null:
value.value = cls._generate_next_value_(None, 1, len(cls.__members__), cls._values[:]) # Note: This just passes None for the key, which is generally okay
value = value.value
args = (value,) + args[1:]
cls._values.append(value)
instance = cls._member_type_.__new__(cls, *args, **kwargs)
instance._value_ = value
return instance
def __format__(self, format_spec):
return str.__format__(str(self), format_spec)
Then anywhere in the code you can just do:
from enum import auto
from collections import namedtuple
class Color(namedtuple('ColorTuple', 'id r g b'), EnumFI):
GREEN = auto(), 0, 255, 0
BLUE = auto(), 0, 0, 255
Example output:
In[4]: Color.GREEN
Out[4]: <Color.GREEN: 1>
In[5]: Color.GREEN.value
Out[5]: 1
In[6]: Color.GREEN.r
Out[6]: 0
In[7]: Color.GREEN.g
Out[7]: 255
In[8]: Color.GREEN.b
Out[8]: 0
In[9]: Color.GREEN.r = 8
Traceback (most recent call last):
File "/home/phil/anaconda3/envs/dl/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-9-914a059d9d3b>", line 1, in <module>
Color.GREEN.r = 8
AttributeError: can't set attribute
In[10]: Color(2)
Out[10]: <Color.BLUE: 2>
In[11]: Color['BLUE']
Out[11]: <Color.BLUE: 2>
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
For keyword-based initialization of attributes, you might try data-enum, a more lightweight implementation of enum with cleaner syntax for some cases, including this one.
from data_enum import DataEnum
class Item(DataEnum):
data_attribute_names = ('a', 'b')
Item.GREEN = Item(a='a', b='b')
Item.BLUE = Item(a='c', b='d')
I should note that I am the author of data-enum, and built it specifically to address this use case.
After searching a lot, I found these two working examples!
That’s it my friends!
Codes…
from enum import Enum
class StatusInt(int, Enum):
READY = (0, "Ready to go!")
ERROR = (1, "Something wrong!")
def __new__(cls, value, description):
obj = int.__new__(cls, value)
obj._value_ = value
obj._description_ = description
return obj
@property
def description(self):
return self._description_
class StatusObj(Enum):
READY = (0, "Ready to go!")
ERROR = (1, "Something wrong!")
def __init__(self, value, description):
self._value_ = value
self._description_ = description
@property
def description(self):
return self._description_
print(str(StatusInt.READY == StatusInt.ERROR))
print(str(StatusInt.READY.value))
print(StatusInt.READY.description)
print(str(StatusObj.READY == StatusObj.ERROR))
print(str(StatusObj.READY.value))
print(StatusObj.READY.description)
Outputs…
False
0
Ready to go!
False
0
Ready to go!
[Ref(s).: https://docs.python.org/3/library/enum.html#when-to-use-new-vs-init , https://docs.python.org/3/library/enum.html#planet ]
enum-properties provides an extension of the Enum base class that allows attributes on enum values and also allows symmetric mapping backwards from attribute values to their enumeration values.
Add properties to Python enumeration values with a simple declarative syntax. Enum Properties is a lightweight extension to Python’s Enum class. Example:
from enum_properties import EnumProperties, p
from enum import auto
class Color(EnumProperties, p('rgb'), p('hex')):
# name value rgb hex
RED = auto(), (1, 0, 0), 'ff0000'
GREEN = auto(), (0, 1, 0), '00ff00'
BLUE = auto(), (0, 0, 1), '0000ff'
# the named p() values in the Enum's inheritance become properties on
# each value, matching the order in which they are specified
Color.RED.rgb == (1, 0, 0)
Color.GREEN.rgb == (0, 1, 0)
Color.BLUE.rgb == (0, 0, 1)
Color.RED.hex == 'ff0000'
Color.GREEN.hex == '00ff00'
Color.BLUE.hex == '0000ff'
Properties may also be symmetrically mapped to enumeration values, using s() values:
from enum_properties import EnumProperties, s
from enum import auto
class Color(EnumProperties, s('rgb'), s('hex', case_fold=True)):
RED = auto(), (1, 0, 0), 'ff0000'
GREEN = auto(), (0, 1, 0), '00ff00'
BLUE = auto(), (0, 0, 1), '0000ff'
# any named s() values in the Enum's inheritance become properties on
# each value, and the enumeration value may be instantiated from the
# property's value
Color((1, 0, 0)) == Color.RED
Color((0, 1, 0)) == Color.GREEN
Color((0, 0, 1)) == Color.BLUE
Color('ff0000') == Color.RED
Color('FF0000') == Color.RED # case_fold makes mapping case insensitive
Color('00ff00') == Color.GREEN
Color('00FF00') == Color.GREEN
Color('0000ff') == Color.BLUE
Color('0000FF') == Color.BLUE
Color.RED.hex == 'ff0000'
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 */
}
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.
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
.
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
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.
Inspired by some of the other answers, I found a way of including additional fields to an enum as ‘transparently’ as possible, overcoming some shortcomings of the other approaches. Everything works the same as if the additional fields weren’t there.
The enum is immutable just like a tuple, the value of the enum is just as it would be without the additional fields, it works just like a normal enum with auto()
, and selecting an enum by value works.
import enum
# Common base class for all enums you want to create with additional fields (you only need this once)
class EnumFI(enum.Enum):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls._values = []
def __new__(cls, *args, **kwargs):
value = args[0]
if isinstance(value, enum.auto):
if value.value == enum._auto_null:
value.value = cls._generate_next_value_(None, 1, len(cls.__members__), cls._values[:]) # Note: This just passes None for the key, which is generally okay
value = value.value
args = (value,) + args[1:]
cls._values.append(value)
instance = cls._member_type_.__new__(cls, *args, **kwargs)
instance._value_ = value
return instance
def __format__(self, format_spec):
return str.__format__(str(self), format_spec)
Then anywhere in the code you can just do:
from enum import auto
from collections import namedtuple
class Color(namedtuple('ColorTuple', 'id r g b'), EnumFI):
GREEN = auto(), 0, 255, 0
BLUE = auto(), 0, 0, 255
Example output:
In[4]: Color.GREEN
Out[4]: <Color.GREEN: 1>
In[5]: Color.GREEN.value
Out[5]: 1
In[6]: Color.GREEN.r
Out[6]: 0
In[7]: Color.GREEN.g
Out[7]: 255
In[8]: Color.GREEN.b
Out[8]: 0
In[9]: Color.GREEN.r = 8
Traceback (most recent call last):
File "/home/phil/anaconda3/envs/dl/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 3326, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-9-914a059d9d3b>", line 1, in <module>
Color.GREEN.r = 8
AttributeError: can't set attribute
In[10]: Color(2)
Out[10]: <Color.BLUE: 2>
In[11]: Color['BLUE']
Out[11]: <Color.BLUE: 2>
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
For keyword-based initialization of attributes, you might try data-enum, a more lightweight implementation of enum with cleaner syntax for some cases, including this one.
from data_enum import DataEnum
class Item(DataEnum):
data_attribute_names = ('a', 'b')
Item.GREEN = Item(a='a', b='b')
Item.BLUE = Item(a='c', b='d')
I should note that I am the author of data-enum, and built it specifically to address this use case.
After searching a lot, I found these two working examples!
That’s it my friends!
Codes…
from enum import Enum
class StatusInt(int, Enum):
READY = (0, "Ready to go!")
ERROR = (1, "Something wrong!")
def __new__(cls, value, description):
obj = int.__new__(cls, value)
obj._value_ = value
obj._description_ = description
return obj
@property
def description(self):
return self._description_
class StatusObj(Enum):
READY = (0, "Ready to go!")
ERROR = (1, "Something wrong!")
def __init__(self, value, description):
self._value_ = value
self._description_ = description
@property
def description(self):
return self._description_
print(str(StatusInt.READY == StatusInt.ERROR))
print(str(StatusInt.READY.value))
print(StatusInt.READY.description)
print(str(StatusObj.READY == StatusObj.ERROR))
print(str(StatusObj.READY.value))
print(StatusObj.READY.description)
Outputs…
False
0
Ready to go!
False
0
Ready to go!
[Ref(s).: https://docs.python.org/3/library/enum.html#when-to-use-new-vs-init , https://docs.python.org/3/library/enum.html#planet ]
enum-properties provides an extension of the Enum base class that allows attributes on enum values and also allows symmetric mapping backwards from attribute values to their enumeration values.
Add properties to Python enumeration values with a simple declarative syntax. Enum Properties is a lightweight extension to Python’s Enum class. Example:
from enum_properties import EnumProperties, p
from enum import auto
class Color(EnumProperties, p('rgb'), p('hex')):
# name value rgb hex
RED = auto(), (1, 0, 0), 'ff0000'
GREEN = auto(), (0, 1, 0), '00ff00'
BLUE = auto(), (0, 0, 1), '0000ff'
# the named p() values in the Enum's inheritance become properties on
# each value, matching the order in which they are specified
Color.RED.rgb == (1, 0, 0)
Color.GREEN.rgb == (0, 1, 0)
Color.BLUE.rgb == (0, 0, 1)
Color.RED.hex == 'ff0000'
Color.GREEN.hex == '00ff00'
Color.BLUE.hex == '0000ff'
Properties may also be symmetrically mapped to enumeration values, using s() values:
from enum_properties import EnumProperties, s
from enum import auto
class Color(EnumProperties, s('rgb'), s('hex', case_fold=True)):
RED = auto(), (1, 0, 0), 'ff0000'
GREEN = auto(), (0, 1, 0), '00ff00'
BLUE = auto(), (0, 0, 1), '0000ff'
# any named s() values in the Enum's inheritance become properties on
# each value, and the enumeration value may be instantiated from the
# property's value
Color((1, 0, 0)) == Color.RED
Color((0, 1, 0)) == Color.GREEN
Color((0, 0, 1)) == Color.BLUE
Color('ff0000') == Color.RED
Color('FF0000') == Color.RED # case_fold makes mapping case insensitive
Color('00ff00') == Color.GREEN
Color('00FF00') == Color.GREEN
Color('0000ff') == Color.BLUE
Color('0000FF') == Color.BLUE
Color.RED.hex == 'ff0000'