Python Enums: substitute `element` with `element.value` - python

I have a simple Enum:
class E(Enum):
A = 'a'
B = 'b'
To access 'a' I must type E.A.value. However, the value is the only thing I need from the Enum object.
How to write an Enum, where 'a' could be accessed just by E.A?

Using an int as value was just an example. It should actually be a user-defined class.
If you mixin a class/type with the Enum, then simply accessing the member itself will give you a subtype of that type:
from enum import Enum
class MyClass:
def __init__(self, color):
self.color = color
class MyEnum(MyClass, Enum):
first = 'red'
second = 'green'
third = 'blue'
and in use:
>>> MyEnum.first
<MyEnum.first: 'red'>
>>> MyEnum.first.color
'red'
>>> type(MyEnum.first)
<enum 'MyEnum'>
>>> isinstance(MyEnum.first, MyClass)
True
Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

I looked around a lot, and I couldn't find a good solution to this problem using the Enum class you're trying to use. However, if you're willing to eschew the idea of using the Enum as a superclass, you can kludge this together:
class Demo:
# something with a 'value' method in it
def __init__(self, val):
self.value = val
def custom_enum(cls):
# class decorator to get __getattribute__() to work properly
# this is necessary because __getattribute__() only exists as an instance method,
# and there seems to be no direct equivalent for class methods
return cls()
#custom_enum
class E:
# first, define our enumerated variables in a dict
_enums = {
'A': Demo('a'),
'B': Demo('b'),
'chicken': Demo('cluck')
}
# then, override __getattribute__() to first get the key from the dict,
# and return the .value property of it
def __getattribute__(self, key):
# because of the decorator, we can't call self._enums or else we get a RecursionError
# therefore, we need to implicitly subclass `object`, and then
# deliberately invoke object.__getattribute__ on self, to access _enums
my_enums = object.__getattribute__(self, '_enums')
return my_enums[key].value
Actually defining the values of your enumerable is as simple as editing the _enums dict. And once you've done so, it should work roughly as you want it to:
>>> E.A
'a'
>>> E.B
'b'
>>> E.chicken
'cluck'
From here you could modify the implementation however necessary (such as returning an AttributeError instead of a KeyError, for example, or overriding __setattr__() to make enum values non-settable, or whatever).

Related

Subclassing Python Enum with `auto()`, string values, and an extra property

I have an Enum subclass that I would like to do two things:
Use the lowercased name of the enum as the value for each item and a property called function. I don't want to have to type this out each time, so I'm using auto().
Optionally, allow the user to pass a custom function name that overrides the lowercased name.
Here is the code I have come up with to do that:
from enum import Enum, auto
class LowerNameEnum(Enum):
def _generate_next_value_(name, start, count, last_values):
print(f"{name=}")
return name.lower()
class Task(str, LowerNameEnum):
def __new__(cls, value, function=None):
obj = str.__new__(cls, value)
obj._value_ = value
obj.function = function or "generic_function"
return obj
DO_SOMETHING = auto()
DO_SOMETHING_ELSE = auto()
DO_ANOTHER_THING = auto(), "do_it_do_it"
THING_4 = auto()
for t in Task:
print(t)
print(" ", t.value)
print(" ", t.function)
But when I run this, I get the following:
$ python myenum.py
name='DO_SOMETHING'
name='DO_SOMETHING_ELSE'
name='THING_4'
Task.DO_SOMETHING
do_something
generic_function
Task.DO_SOMETHING_ELSE
do_something_else
generic_function
Task.DO_ANOTHER_THING
<enum.auto object at 0x103000160>
do_it_do_it
Task.THING_4
thing_4
generic_function
When I try to use the auto() value in combination with the extra argument, I get what looks like a __repr__ of an enum.auto object instead of the lowercased item name. This is because _generate_next_value_ isn't even called for this one, despite my having passed auto().
What am I doing wrong?
Update: Well, it turns out that value in the __new__ method for the third item is an enum.auto object. So that's how it's getting set as the value. I can use isinstance to capture this, which is good. But then my question becomes: how do I find out the name of the item at this point so I can pass it to _generate_next_value_? Or is there some better way to do this?
Until this is fixed (in 3.12), you'll need to use aenum:
from aenum import Enum
...
class Task(str, LowerNameEnum):
...
DO_ANOTHER_THING = auto(function="do_it_do_it")
Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

Python: how to share class attribute between derived classes?

I want to share some information between all the instances of some class and all it's derived classes.
class Base():
cv = "some value" # information I want to share
def print_cv(self, note):
print("{}: {}".format(note, self.cv))
#classmethod
def modify_cv(cls, new_value):
# do some class-specific stuff
cls.cv = new_value
class Derived(Base):
pass
b = Base()
d = Derived()
b.print_cv("base")
d.print_cv("derived")
Output is as expected (instances of both classes see correct class attribute):
base: some value
derived: some value
I can change the value of this class attribute and everything is still fine:
# Base.cv = "new value"
b.modify_cv("new value")
b.print_cv("base") # -> base: new value
d.print_cv("derived") # -> derived: new value
So far so good. The problem is that the "connection" between Base and Derived classes can be broken if I access cv via derived class:
# Derived.cv = "derived-specific value"
d.modify_cv("derived-specific value")
b.print_cv("base") # -> base: new value
d.print_cv("derived") # -> derived: derived-specific value
This behavior is expected, but this is not what I want!
I understand why a and b see different values of cv - because they are instances of different classes. I have overridden cv value in derived class and now derived class behaves differently, I've used this feature many times.
But for my current task I need a and b always use the same cv!
UPDATE
I have updated the question and now it better describes the real-life situation. Actually I did not modify cv value like this:
Base.cv = "new value"
modifications were done in some classmethods (actually all these class methods were implemented in Base class).
And now solution became obvious, I just need to modify the method slightly:
class Base():
#classmethod
def modify_cv(cls, new_value):
#cls.cv = new_value
Base.cv = new_value
Thank you all for discussion and ideas (in the begining I was going to use getters/setters and module-level attribute)
classmethod is useful when you need to know which class is calling the method, but if you want the same behaviour regardless of the class that's calling the method, you could use staticmethod instead. You can then access the class variable simply through the base class's name with Base.cv:
class Base:
cv = "some value" # information I want to share
def print_cv(self, note):
print("{}: {}".format(note, self.cv))
#staticmethod
def modify_cv(new_value):
Base.cv = new_value
You can still call it on any instance or subclass, but it always changes Base.cv:
>>> b = Base()
>>> d = Derived()
>>> Base.cv == Derived.cv == b.cv == d.cv == "some value"
True
>>> d.modify_cv("new value")
>>> Base.cv == Derived.cv == b.cv == d.cv == "new value"
True
Update:
If you still need access to the class for other reasons, use classmethod with the cls argument as you did before, but still access the base class's variable through Base.cv rather than cls.cv:
#classmethod
def modify_cv(cls, new_value):
do_stuff_with(cls)
Base.cv = new_value
You have to override __setattr__ on the class of the class, i.e. a metaclass:
class InheritedClassAttributesMeta(type):
def __setattr__(self, key, value):
cls = None
if not hasattr(self, key):
# The attribute doesn't exist anywhere yet,
# so just set it here
cls = self
else:
# Find the base class that's actually storing it
for cls in self.__mro__:
if key in cls.__dict__:
break
type.__setattr__(cls, key, value)
class Base(metaclass=InheritedClassAttributesMeta):
cv = "some value"
class Derived(Base):
pass
print(Derived.cv)
Derived.cv = "other value"
print(Base.cv)
Using metaclasses is often overkill, so directly specifying Base might be better.
To avoid unwanted side effects with this solution, consider checking first if key is in some predefined set of attribute names before changing the behaviour.
In Python, inside a method, you can use the bare __class__ variable name to mean the actual class the method is defined in.
This differs from the cls arg that is passed to classmethods, or self.__class__ on regular methods, that will refer to a subclass if the method is invoked in a subclass. Thus, cls.attr = value would set the value on the subclass class' __dict__, and the attribute value will be independent on that subclass from that point on. This is what you are getting there.
Instead, you can use:
class MyClass:
cv = "value"
#classmethod # this is actually optional
def modify_cv(cls, new_value):
__class__.cv = new_value
__class__ is created automatically in Python 3 by
the mechanism that allows one to write
parameterless form of super

How to extend Python Enum?

Is it possible to extend classes created using the new Enum functionality in Python 3.4? How?
Simple subclassing doesn't appear to work. An example like
from enum import Enum
class EventStatus(Enum):
success = 0
failure = 1
class BookingStatus(EventStatus):
duplicate = 2
unknown = 3
will give an exception like TypeError: Cannot extend enumerations or (in more recent versions) TypeError: BookingStatus: cannot extend enumeration 'EventStatus'.
How can I make it so that BookingStatus reuses the enumeration values from EventStatus and adds more?
Subclassing an enumeration is allowed only if the enumeration does not define any members.
Allowing subclassing of enums that define members would lead to a violation of some important invariants of types and instances.
https://docs.python.org/3/howto/enum.html#restricted-enum-subclassing
So no, it's not directly possible.
While uncommon, it is sometimes useful to create an enum from many modules. The aenum1 library supports this with an extend_enum function:
from aenum import Enum, extend_enum
class Index(Enum):
DeviceType = 0x1000
ErrorRegister = 0x1001
for name, value in (
('ControlWord', 0x6040),
('StatusWord', 0x6041),
('OperationMode', 0x6060),
):
extend_enum(Index, name, value)
assert len(Index) == 5
assert list(Index) == [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode]
assert Index.DeviceType.value == 0x1000
assert Index.StatusWord.value == 0x6041
1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.
Calling the Enum class directly and making use of chain allows the extension (joining) of an existing enum.
I came upon the problem of extending enums while working on a CANopen
implementation. Parameter indices in the range from 0x1000 to 0x2000
are generic to all CANopen nodes while e.g. the range from 0x6000
onwards depends open whether the node is a drive, io-module, etc.
nodes.py:
from enum import IntEnum
class IndexGeneric(IntEnum):
""" This enum holds the index value of genric object entrys
"""
DeviceType = 0x1000
ErrorRegister = 0x1001
Idx = IndexGeneric
drives.py:
from itertools import chain
from enum import IntEnum
from nodes import IndexGeneric
class IndexDrives(IntEnum):
""" This enum holds the index value of drive object entrys
"""
ControlWord = 0x6040
StatusWord = 0x6041
OperationMode = 0x6060
Idx= IntEnum('Idx', [(i.name, i.value) for i in chain(IndexGeneric,IndexDrives)])
I tested that way on 3.8. We may inherit existing enum but we need to do it also from base class (at last position).
Docs:
A new Enum class must have one base Enum class, up to one concrete
data type, and as many object-based mixin classes as needed. The order
of these base classes is:
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
Example:
class Cats(Enum):
SIBERIAN = "siberian"
SPHINX = "sphinx"
class Animals(Cats, Enum):
LABRADOR = "labrador"
CORGI = "corgi"
After that you may access Cats from Animals:
>>> Animals.SIBERIAN
<Cats.SIBERIAN: 'siberian'>
But if you want to iterate over this enum, only new members were accessible:
>>> list(Animals)
[<Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]
Actually this way is for inheriting methods from base class, but you may use it for members with these restrictions.
Another way (a bit hacky)
As described above, to write some function to join two enums in one. I've wrote that example:
def extend_enum(inherited_enum):
def wrapper(added_enum):
joined = {}
for item in inherited_enum:
joined[item.name] = item.value
for item in added_enum:
joined[item.name] = item.value
return Enum(added_enum.__name__, joined)
return wrapper
class Cats(Enum):
SIBERIAN = "siberian"
SPHINX = "sphinx"
#extend_enum(Cats)
class Animals(Enum):
LABRADOR = "labrador"
CORGI = "corgi"
But here we meet another problems. If we want to compare members it fails:
>>> Animals.SIBERIAN == Cats.SIBERIAN
False
Here we may compare only names and values of newly created members:
>>> Animals.SIBERIAN.value == Cats.SIBERIAN.value
True
But if we need iteration over new Enum, it works ok:
>>> list(Animals)
[<Animals.SIBERIAN: 'siberian'>, <Animals.SPHINX: 'sphinx'>, <Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]
So choose your way: simple inheritance, inheritance emulation with decorator (recreation in fact), or adding a new dependency like aenum (I haven't tested it, but I expect it support all features I described).
For correct type specification, you could use the Union operator:
from enum import Enum
from typing import Union
class EventStatus(Enum):
success = 0
failure = 1
class BookingSpecificStatus(Enum):
duplicate = 2
unknown = 3
BookingStatus = Union[EventStatus, BookingSpecificStatus]
example_status: BookingStatus
example_status = BookingSpecificStatus.duplicate
example_status = EventStatus.success
Plenty of good answers here already but here's another one purely using Enum's Functional API.
Probably not the most beautiful solution but it avoids code duplication, works out of the box, no additional packages/libraries are need, and it should be sufficient to cover most use cases:
from enum import Enum
class EventStatus(Enum):
success = 0
failure = 1
BookingStatus = Enum(
"BookingStatus",
[es.name for es in EventStatus] + ["duplicate", "unknown"],
start=0,
)
for bs in BookingStatus:
print(bs.name, bs.value)
# success 0
# failure 1
# duplicate 2
# unknown 3
If you'd like to be explicit about the values assigned, you can use:
BookingStatus = Enum(
"BookingStatus",
[(es.name, es.value) for es in EventStatus] + [("duplicate", 6), ("unknown", 7)],
)
for bs in BookingStatus:
print(bs.name, bs.value)
# success 0
# failure 1
# duplicate 6
# unknown 7
I've opted to use a metaclass approach to this problem.
from enum import EnumMeta
class MetaClsEnumJoin(EnumMeta):
"""
Metaclass that creates a new `enum.Enum` from multiple existing Enums.
#code
from enum import Enum
ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
pass
print(ENUMJOINED.a)
print(ENUMJOINED.b)
print(ENUMJOINED.c)
print(ENUMJOINED.d)
#endcode
"""
#classmethod
def __prepare__(metacls, name, bases, enums=None, **kargs):
"""
Generates the class's namespace.
#param enums Iterable of `enum.Enum` classes to include in the new class. Conflicts will
be resolved by overriding existing values defined by Enums earlier in the iterable with
values defined by Enums later in the iterable.
"""
#kargs = {"myArg1": 1, "myArg2": 2}
if enums is None:
raise ValueError('Class keyword argument `enums` must be defined to use this metaclass.')
ret = super().__prepare__(name, bases, **kargs)
for enm in enums:
for item in enm:
ret[item.name] = item.value #Throws `TypeError` if conflict.
return ret
def __new__(metacls, name, bases, namespace, **kargs):
return super().__new__(metacls, name, bases, namespace)
#DO NOT send "**kargs" to "type.__new__". It won't catch them and
#you'll get a "TypeError: type() takes 1 or 3 arguments" exception.
def __init__(cls, name, bases, namespace, **kargs):
super().__init__(name, bases, namespace)
#DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older. You'll get a
#"TypeError: type.__init__() takes no keyword arguments" exception.
This metaclass can be used like so:
>>> from enum import Enum
>>>
>>> ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
>>> ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
>>> class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
... e = 5
... f = 6
...
>>> print(repr(ENUMJOINED.a))
<ENUMJOINED.a: 1>
>>> print(repr(ENUMJOINED.b))
<ENUMJOINED.b: 2>
>>> print(repr(ENUMJOINED.c))
<ENUMJOINED.c: 3>
>>> print(repr(ENUMJOINED.d))
<ENUMJOINED.d: 4>
>>> print(repr(ENUMJOINED.e))
<ENUMJOINED.e: 5>
>>> print(repr(ENUMJOINED.f))
<ENUMJOINED.f: 6>
This approach creates a new Enum using the same name-value pairs as the source Enums, but the resulting Enum members are still unique. The names and values will be the same, but they will fail direct comparisons to their origins following the spirit of Python's Enum class design:
>>> ENUMA.b.name == ENUMJOINED.b.name
True
>>> ENUMA.b.value == ENUMJOINED.b.value
True
>>> ENUMA.b == ENUMJOINED.b
False
>>> ENUMA.b is ENUMJOINED.b
False
>>>
Note what happens in the event of a namespace conflict:
>>> ENUMC = Enum('ENUMA', {'a': 1, 'b': 2})
>>> ENUMD = Enum('ENUMB', {'a': 3})
>>> class ENUMJOINEDCONFLICT(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMC, ENUMD)):
... pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 19, in __prepare__
File "C:\Users\jcrwfrd\AppData\Local\Programs\Python\Python37\lib\enum.py", line 100, in __setitem__
raise TypeError('Attempted to reuse key: %r' % key)
TypeError: Attempted to reuse key: 'a'
>>>
This is due to the base enum.EnumMeta.__prepare__ returning a special enum._EnumDict instead of the typical dict object that behaves different upon key assignment. You may wish to suppress this error message by surrounding it with a try-except TypeError, or there may be a way to modify the namespace before calling super().__prepare__(...).
Another way :
Letter = Enum(value="Letter", names={"A": 0, "B": 1})
LetterExtended = Enum(value="Letter", names=dict({"C": 2, "D": 3}, **{i.name: i.value for i in Letter}))
Or :
LetterDict = {"A": 0, "B": 1}
Letter = Enum(value="Letter", names=LetterDict)
LetterExtendedDict = dict({"C": 2, "D": 3}, **LetterDict)
LetterExtended = Enum(value="Letter", names=LetterExtendedDict)
Output :
>>> Letter.A
<Letter.A: 0>
>>> Letter.C
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "D:\jhpx\AppData\Local\Programs\Python\Python36\lib\enum.py", line 324, in __getattr__
raise AttributeError(name) from None
AttributeError: C
>>> LetterExtended.A
<Letter.A: 0>
>>> LetterExtended.C
<Letter.C: 2>
I think you could do it in this way:
from typing import List
from enum import Enum
def extend_enum(current_enum, names: List[str], values: List = None):
if not values:
values = names
for item in current_enum:
names.append(item.name)
values.append(item.value)
return Enum(current_enum.__name__, dict(zip(names, values)))
class EventStatus(Enum):
success = 0
failure = 1
class BookingStatus(object):
duplicate = 2
unknown = 3
BookingStatus = extend_enum(EventStatus, ['duplicate','unknown'],[2,3])
the key points is:
python could change anything at runtime
class is object too
You can't extend enums but you can create a new one by merging them.
Tested for Python 3.6
from enum import Enum
class DummyEnum(Enum):
a = 1
class AnotherDummyEnum(Enum):
b = 2
def merge_enums(class_name: str, enum1, enum2, result_type=Enum):
if not (issubclass(enum1, Enum) and issubclass(enum2, Enum)):
raise TypeError(
f'{enum1} and {enum2} must be derived from Enum class'
)
attrs = {attr.name: attr.value for attr in set(chain(enum1, enum2))}
return result_type(class_name, attrs, module=__name__)
result_enum = merge_enums(
class_name='DummyResultEnum',
enum1=DummyEnum,
enum2=AnotherDummyEnum,
)
Decorator to extend Enum
To expand on Mikhail Bulygin's answer, a decorator can be used to extend an Enum (and support equality by using a custom Enum base class).
1. Enum base class with value-based equality
from enum import Enum
from typing import Any
class EnumBase(Enum):
def __eq__(self, other: Any) -> bool:
if isinstance(other, Enum):
return self.value == other.value
return False
2. Decorator to extend Enum class
from typing import Callable
def extend_enum(parent_enum: EnumBase) -> Callable[[EnumBase], EnumBase]:
"""Decorator function that extends an enum class with values from another enum class."""
def wrapper(extended_enum: EnumBase) -> EnumBase:
joined = {}
for item in parent_enum:
joined[item.name] = item.value
for item in extended_enum:
joined[item.name] = item.value
return EnumBase(extended_enum.__name__, joined)
return wrapper
Example
>>> from enum import Enum
>>> from typing import Any, Callable
>>> class EnumBase(Enum):
def __eq__(self, other: Any) -> bool:
if isinstance(other, Enum):
return self.value == other.value
return False
>>> def extend_enum(parent_enum: EnumBase) -> Callable[[EnumBase], EnumBase]:
def wrapper(extended_enum: EnumBase) -> EnumBase:
joined = {}
for item in parent_enum:
joined[item.name] = item.value
for item in extended_enum:
joined[item.name] = item.value
return EnumBase(extended_enum.__name__, joined)
return wrapper
>>> class Parent(EnumBase):
A = 1
B = 2
>>> #extend_enum(Parent)
class ExtendedEnum(EnumBase):
C = 3
>>> Parent.A == ExtendedEnum.A
True
>>> list(ExtendedEnum)
[<ExtendedEnum.A: 1>, <ExtendedEnum.B: 2>, <ExtendedEnum.C: 3>]
Yes, you can modify an Enum. The example code, below, is somewhat hacky and it obviously depends on internals of Enum which it has no business whatsoever to depend on. On the other hand, it works.
class ExtIntEnum(IntEnum):
#classmethod
def _add(cls, value, name):
obj = int.__new__(cls, value)
obj._value_ = value
obj._name_ = name
obj.__objclass__ = cls
cls._member_map_[name] = obj
cls._value2member_map_[value] = obj
cls._member_names_.append(name)
class Fubar(ExtIntEnum):
foo = 1
bar = 2
Fubar._add(3,"baz")
Fubar._add(4,"quux")
Specifically, observe the obj = int.__new__() line. The enum module jumps through a few hoops to find the correct __new__ method for the class that should be enumerated. We ignore these hoops here because we already know how integers (or rather, instances of subclasses of int) are created.
It's a good idea not to use this in production code. If you have to, you really should add guards against duplicate values or names.
I wanted to inherit from Django's IntegerChoices which is not possible due to the "Cannot extend enumerations" limitation. I figured it could be done by a relative simple metaclass.
CustomMetaEnum.py:
class CustomMetaEnum(type):
def __new__(self, name, bases, namespace):
# Create empty dict to hold constants (ex. A = 1)
fields = {}
# Copy constants from the namespace to the fields dict.
fields = {key:value for key, value in namespace.items() if isinstance(value, int)}
# In case we're about to create a subclass, copy all constants from the base classes' _fields.
for base in bases:
fields.update(base._fields)
# Save constants as _fields in the new class' namespace.
namespace['_fields'] = fields
return super().__new__(self, name, bases, namespace)
# The choices property is often used in Django.
# If other methods such as values(), labels() etc. are needed
# they can be implemented below (for inspiration [Django IntegerChoice source][1])
#property
def choices(self):
return [(value,key) for key,value in self._fields.items()]
main.py:
from CustomMetaEnum import CustomMetaEnum
class States(metaclass=CustomMetaEnum):
A = 1
B = 2
C = 3
print("States: ")
print(States.A)
print(States.B)
print(States.C)
print(States.choices)
print("MoreStates: ")
class MoreStates(States):
D = 22
pass
print(MoreStates.A)
print(MoreStates.B)
print(MoreStates.C)
print(MoreStates.D)
print(MoreStates.choices)
python3.8 main.py:
States:
1
2
3
[(1, 'A'), (2, 'B'), (3, 'C')]
MoreStates:
1
2
3
22
[(22, 'D'), (1, 'A'), (2, 'B'), (3, 'C')]
Conceptually, it does not make sense to extend an enumeration in this sense. The problem is that this violates the Liskov Substitution Principle: instances of a subclass are supposed to be usable anywhere an instance of the base class could be used, but an instance of BookingStatus could not reliably be used anywhere that an EventStatus is expected. After all, if that instance had a value of BookingStatus.duplicate or BookingStatus.unknown, that would not be a valid enumeration value for an EventStatus.
We can create a new class that reuses the EventStatus setup by using the functional API. A basic example:
event_status_codes = [s.name for s in EventStatus]
BookingStatus = Enum(
'BookingStatus', event_status_codes + ['duplicate', 'unknown']
)
This approach re-numbers the enumeration values, ignoring what they were in EventStatus. We can also pass name-value pairs in order to specify the enum values; this lets us do a bit more analysis, in order to reuse the old values and auto-number new ones:
def extend_enum(result_name, base, *new_names):
base_values = [(v.name, v.value) for v in base]
next_number = max(v.value for v in base) + 1
new_values = [(name, i) for i, name in enumerate(new_names, next_number)]
return Enum(result_name, base_values + new_values)
# Now we can do:
BookingStatus = extend_enum('BookingStatus', EventStatus, 'duplicate', 'unknown')

Python 3 Enums with Function Values

I noticed an oddity in the Python 3 Enums (link).
If you set the value of an Enum to a function, it prevents the attribute from being wrapped as an Enum object, which prevents you from being able to use the cool features like EnumCls['AttrName'] to dynamically load the attribute.
Is this a bug? Done on purpose?
I searched for a while but found no mention of restricted values that you can use in an Enum.
Here is sample code that displays the issue:
class Color(Enum):
Red = lambda: print('In Red')
Blue = lambda: print('In Blue')
print(Color.Red) # <function> - should be Color.Red via Docs
print(Color.Blue) # <function> - should be Color.Bluevia Docs
print(Color['Red']) # throws KeyError - should be Color.Red via Docs
Also, this is my first time asking, so let me know if there's anything I should be doing differently! And thanks for the help!
You can override the __call__ method:
from enum import Enum, auto
class Color(Enum):
red = auto()
blue = auto()
def __call__(self, *args, **kwargs):
return f'<font color={self.name}>{args[0]}</font>'
Can then be used:
>>> Color.red('flowers')
<font color=red>flowers</font>
The documentation says:
The rules for what is allowed are as follows: _sunder_ names (starting and ending with a single underscore) are reserved by enum and cannot be used; all other attributes defined within an enumeration will become members of this enumeration, with the exception of __dunder__ names and descriptors (methods are also descriptors).
A "method" is just a function defined inside a class body. It doesn't matter whether you define it with lambda or def. So your example is the same as:
class Color(Enum):
def Red():
print('In Red')
def Blue():
print('In Blue')
In other words, your purported enum values are actually methods, and so won't become members of the Enum.
If someone need/want to use Enum with functions as values, its possible to do so by using a callable object as a proxy, something like this:
class FunctionProxy:
"""Allow to mask a function as an Object."""
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
return self.function(*args, **kwargs)
A simple test:
from enum import Enum
class Functions(Enum):
Print_Function = FunctionProxy(lambda *a: print(*a))
Split_Function = FunctionProxy(lambda s, d='.': s.split(d))
Functions.Print_Function.value('Hello World!')
# Hello World!
Functions.Split_Function.value('Hello.World.!')
# ['Hello', 'World', '!']
You can also use functools.partial to trick the enum into not considering your function a method of Color:
from functools import partial
from enum import Enum
class Color(Enum):
Red = partial(lambda: print('In Red'))
Blue = partial(lambda: print('In Blue'))
With this you can access name and value as expected.
Color.Red
Out[17]: <Color.Red: functools.partial(<function Color.<lambda> at 0x7f84ad6303a0>)>
Color.Red.name
Out[18]: 'Red'
Color.Red.value()
In Red
I ran into this issue recently, found this post, and first was tempted to use the wrapper pattern suggested in the other related post. However eventually I found out that this was a bit overkill for what I had to do. In the past years this happened to me several times with Enum, so I would like to share this simple experience feedback:
if you need an enumeration, ask yourself whether you actually need an enum or just a namespace.
The difference is simple: Enum members are instances of their host enum class, while namespace members are completely independent from the class, they are just located inside.
Here is an example of namespace containing callables, with a get method to return any of them by name.
class Foo(object):
""" A simple namespace class with a `get` method to access members """
#classmethod
def get(cls, member_name: str):
"""Get a member by name"""
if not member_name.startswith('__') and member_name != 'get':
try:
return getattr(cls, member_name)
except AttributeError:
pass
raise ValueError("Unknown %r member: %r" % (cls.__name__, member_name))
# -- the "members" --
a = 1
#staticmethod
def welcome(name):
return "greetings, %s!" % name
#staticmethod
def wave(name):
return "(silently waving, %s)" % name
w = Foo.get('welcome')
a = Foo.get('a')
Foo.get('unknown') # ValueError: Unknown 'Foo' member: 'unknown'
See also this post on namespaces.
Initially, I thought your issue was just missing commas because I got the output you were expecting.:
from enum import Enum
class Color(Enum):
Red = lambda: print('In Red'),
Blue = lambda: print('In Blue'),
print(Color.Red)
print(Color.Blue)
print(Color['Red'])
output (python3.7)
$ /usr/local/opt/python/bin/python3.7 ~/test_enum.py
Color.Red
Color.Blue
Color.Red
#BernBarn was kind enough to explain that in my solution that a tuple is being created, and to invoke the function would require dereferencing value[0]. There is already another answer using value[0] in this way. I miss rb for this.

Overriding __contains__ method for a class

I need to simulate enums in Python, and did it by writing classes like:
class Spam(Enum):
k = 3
EGGS = 0
HAM = 1
BAKEDBEANS = 2
Now I'd like to test if some constant is a valid choice for a particular Enum-derived class, with the following syntax:
if (x in Foo):
print("seems legit")
Therefore I tried to create an "Enum" base class where I override the __contains__ method like this:
class Enum:
"""
Simulates an enum.
"""
k = 0 # overwrite in subclass with number of constants
#classmethod
def __contains__(cls, x):
"""
Test for valid enum constant x:
x in Enum
"""
return (x in range(cls.k))
However, when using the in keyword on the class (like the example above), I get the error:
TypeError: argument of type 'type' is not iterable
Why that? Can I somehow get the syntactic sugar I want?
Why that?
When you use special syntax like a in Foo, the __contains__ method is looked up on the type of Foo. However, your __contains__ implementation exists on Foo itself, not its type. Foo's type is type, which doesn't implement this (or iteration), thus the error.
The same situation occurs if you instantiate an object and then, after it is created, add a __contains__ function to the instance variables. That function won't be called:
>>> class Empty: pass
...
>>> x = Empty()
>>> x.__contains__ = lambda: True
>>> 1 in x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument of type 'Empty' is not iterable
Can I somehow get the syntactic sugar I want?
Yes. As mentioned above, the method is looked up on Foo's type. The type of a class is called a metaclass, so you need a new metaclass that implements __contains__.
Try this one:
class MetaEnum(type):
def __contains__(cls, x):
return x in range(cls.k)
As you can see, the methods on a metaclass take the metaclass instance -- the class -- as their first argument. This should make sense. It's very similar to a classmethod, except that the method lives on the metaclass and not the class.
Inheritance from a class with a custom metaclass also inherits the metaclass, so you can create a base class like so:
class BaseEnum(metaclass=MetaEnum):
pass
class MyEnum(BaseEnum):
k = 3
print(1 in MyEnum) # True
My usecase was to test on the names of the members of my Enum.
With a slight modification to this solution:
from enum import Enum, EnumMeta, auto
class MetaEnum(EnumMeta):
def __contains__(cls, item):
return item in cls.__members__.keys()
class BaseEnum(Enum, metaclass=MetaEnum):
pass
class LogSections(BaseEnum):
configuration = auto()
debug = auto()
errors = auto()
component_states = auto()
alarm = auto()
if __name__ == "__main__":
print('configuration' in LogSections)
print('b' in LogSections)
True
False

Categories