Let's say, I have a pre-existing mapping as a dictionary:
value_map = {'a': 1, 'b': 2}
I can create an enum class from this like so:
from enum import Enum
MyEnum = Enum('MyEnum', value_map)
and use it like so
a = MyEnum.a
print(a.value)
>>> 1
print(a.name)
>>> 'a'
But then I want to define some methods to my new enum class:
def double_value(self):
return self.value * 2
Of course, i can do this:
class MyEnum(Enum):
a = 1
b = 2
#property
def double_value(self):
return self.value * 2
But as I said, I have to use a pre-defined value mapping dictionary, so I cannot do this.
How can this be achieved? I tried to inherit from another class defining this method like a mixin, but I could'nt figure it out.
You can pass in a base type with mixin methods into the functional API, with the type argument:
>>> import enum
>>> value_map = {'a': 1, 'b': 2}
>>> class DoubledEnum:
... #property
... def double_value(self):
... return self.value * 2
...
>>> MyEnum = enum.Enum('MyEnum', value_map, type=DoubledEnum)
>>> MyEnum.a.double_value
2
For a fully functional approach that never uses a class statement, you can create the base mix-in with the type() function:
DoubledEnum = type('DoubledEnum', (), {'double_value': property(double_value)})
MyEnum = enum.Enum('MyEnum', value_map, type=DoubledEnum)
You can also use enum.EnumMeta() metaclass the same way, the way Python would when you create a class MyEnum(enum.Enum): ... subclass:
Create a class dictionary using the metaclass __prepare__ hook
Call the metaclass, passing in the class name, the bases ((enum.Enum,) here), and the class dictionary created in step 1.
The custom dictionary subclass that enum.EnumMeta uses isn't really designed for easy reuse; it implements a __setitem__ hook to record metadata, but doesn't override the dict.update() method, so we need to use a little care when using your value_map dictionary:
import enum
def enum_with_extras(name, value_map, bases=enum.Enum, **extras):
if not isinstance(bases, tuple):
bases = bases,
if not any(issubclass(b, enum.Enum) for b in bases):
bases += enum.Enum,
classdict = enum.EnumMeta.__prepare__(name, bases)
for key, value in {**value_map, **extras}.items():
classdict[key] = value
return enum.EnumMeta(name, bases, classdict)
Then pass in double_value=property(double_value) to that function (together with the enum name and value_map dictionary):
>>> def double_value(self):
... return self.value * 2
...
>>> MyEnum = enum_with_extras('MyEnum', value_map, double_value=property(double_value))
>>> MyEnum.a
<MyEnum.a: 1>
>>> MyEnum.a.double_value
2
You are otherwise allowed to create subclasses of an enum without members (anything that's a descriptor is not a member, so functions, properties, classmethods, etc.), so you can define an enum without members first:
class DoubledEnum(enum.Enum):
#property
def double_value(self):
return self.value * 2
which is an acceptable base class for both in the functional API (e.g. enum.Enum(..., type=DoubledEnum)) and for the metaclass approach I encoded as enum_with_extras().
You can create a new meta class (Either using a meta-metaclass or a factory function, like I do below) that derives from enum.EnumMeta (The metaclass for enums) and just adds the members before creating the class
import enum
import collections.abc
def enum_metaclass_with_default(default_members):
"""Creates an Enum metaclass where `default_members` are added"""
if not isinstance(default_members, collections.abc.Mapping):
default_members = enum.Enum('', default_members).__members__
default_members = dict(default_members)
class EnumMetaWithDefaults(enum.EnumMeta):
def __new__(mcs, name, bases, classdict):
"""Updates classdict adding the default members and
creates a new Enum class with these members
"""
# Update the classdict with default_members
# if they don't already exist
for k, v in default_members.items():
if k not in classdict:
classdict[k] = v
# Add `enum.Enum` as a base class
# Can't use `enum.Enum` in `bases`, because
# that uses `==` instead of `is`
bases = tuple(bases)
for base in bases:
if base is enum.Enum:
break
else:
bases = (enum.Enum,) + bases
return super(EnumMetaWithDefaults, mcs).__new__(mcs, name, bases, classdict)
return EnumMetaWithDefaults
value_map = {'a': 1, 'b': 2}
class MyEnum(metaclass=enum_metaclass_with_default(value_map)):
#property
def double_value(self):
return self.value * 2
assert MyEnum.a.double_value == 2
A different solution was to directly try and update locals(), as it is replaced with a mapping that creates enum values when you try to assign values.
import enum
value_map = {'a': 1, 'b': 2}
def set_enum_values(locals, value_map):
# Note that we can't use `locals.update(value_map)`
# because it's `locals.__setitem__(k, v)` that
# creates the enum value, and `update` doesn't
# call `__setitem__`.
for k, v in value_map:
locals[k] = v
class MyEnum(enum.Enum):
set_enum_values(locals(), value_map)
#property
def double_value(self):
return self.value * 2
assert MyEnum.a.double_value == 2
This seems well defined enough, and a = 1 is most likely going to be the same as locals()['a'] = 1, but it might change in the future. The first solution is more robust and less hacky (And I haven't tested it in other Python implementations, but it probably works the same)
PLUS: Adding more stuff (a dirt hack) to #Artyer's answer. 🤗
Note that you can also provide "additional" capabilities to an Enum if you create it from a dict, see...
from enum import Enum
_colors = {"RED": (1, "It's the color of blood."), "BLUE": (2, "It's the color of the sky.")}
def _set_members_colors(locals: dict):
for k, v in colors.items():
locals[k] = v[0]
class Colors(int, Enum):
_set_members_colors(locals())
#property
def description(self):
return colors[self.name][1]
print(str(Colors.RED))
print(str(Colors.RED.value))
print(str(Colors.RED.description))
Output...
Colors.RED
1
It's the color of blood.
Thanks! 😉
Related
I am new to Python's metaclasses and I am currently trying to achieve the following:
from enum import Enum, EnumMeta
class FlexEnumMeta(EnumMeta):
# see the attempt for the implementation below
...
class FlexEnum(Enum, metaclass=FlexEnumMeta, attrs=('foo', 'bar')):
X = 'abc', 123
Y = 'def', 456
and I would expect it to work as follow:
FlexEnum.X.foo
>>> 'abc'
I read parts of the source code of Enum and aenum to try to understand, and as far as I got was with this code for FlexEnumMeta:
class FlexEnumMeta(EnumMeta):
#classmethod
def __prepare__(metacls, cls, bases, attrs=None, **kwargs):
return {}
def __new__(metacls, name, bases, clsdict, attrs=None, **kwargs):
# Proper way to get an _EnumDict,
# to be passed to super().__new__
enum_dict = super().__prepare__(name, bases, **kwargs)
# Enumeration class members
members = []
for member_name, values in clsdict.items():
# Copy original clsdict in final class members list
if member_name.startswith('_'):
enum_dict[member_name] = values
continue
# Create correspondance between attributes names and values
value_dict = dict(zip(attrs, values))
members.append((member_name, value_dict))
# Copy custom class members in final class members list
for key, value in members:
enum_dict[key] = value
return super().__new__(metacls, name, bases, enum_dict)
class FlexEnum(Enum, metaclass=FlexEnumMeta, attrs=('foo', 'bar')):
X = 'abc', 123
FlexEnum.X
>>> <FlexEnum.X: {'foo': 'abc', 'bar': 123}>
FlexEnum.X.value['foo']
>>> 'abc'
I tried using aenum's MultiValue setting, but I need to have a dict as one of my attributes, and those aren't hashable.
And in the end, this is kind of an exercise for me.
All you need is to define FlexEnum.__init__:
import enum
class FlexEnum(enum.Enum):
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
X = 'abc', 123
Y = 'def', 456
Then
>>> FlexEnum.X.foo
'abc'
The value of the member itself will be the tuple. If you'd like something else, you can override __new__. There is some more information in the documentation, When to use __new__() vs. __init__().
I have an abstract base class GameNodeState that contains a Type enum:
import abc
import enum
class GameNodeState(metaclass=abc.ABCMeta):
class Type(enum.Enum):
INIT = enum.auto()
INTERMEDIATE = enum.auto()
END = enum.auto()
The names in the enum are generic because they must make sense for any subclass of GameNodeState. But when I subclass GameNodeState, as GameState and RoundState, I would like to be able to add concrete aliases to the members of GameNodeState.Type if the enum is accessed through the subclass. For example, if the GameState subclass aliases INTERMEDIATE as ROUND and RoundState aliases INTERMEDIATE as TURN, I would like the following behaviour:
>>> GameNodeState.Type.INTERMEDIATE
<Type.INTERMEDIATE: 2>
>>> RoundState.Type.TURN
<Type.INTERMEDIATE: 2>
>>> RoundState.Type.INTERMEDIATE
<Type.INTERMEDIATE: 2>
>>> GameNodeState.Type.TURN
AttributeError: TURN
My first thought was this:
class GameState(GameNodeState):
class Type(GameNodeState.Type):
ROUND = GameNodeState.Type.INTERMEDIATE.value
class RoundState(GameNodeState):
class Type(GameNodeState.Type):
TURN = GameNodeState.Type.INTERMEDIATE.value
But enums can't be subclassed.
Note: there are obviously more attributes and methods in the GameNodeState hierarchy, I stripped it down to the bare minimum here to focus on this particular thing.
Refinement
(Original solution below.)
I've extracted an intermediate concept from the code above, namely the concept of enum union. This can be used to obtain the behaviour above, and is also useful in other contexts too. The code can be foud here, and I've asked a Code Review question.
I'll add the code here as well for reference:
import enum
import itertools as itt
from functools import reduce
import operator
from typing import Literal, Union
import more_itertools as mitt
AUTO = object()
class UnionEnumMeta(enum.EnumMeta):
"""
The metaclass for enums which are the union of several sub-enums.
Union enums have the _subenums_ attribute which is a tuple of the enums forming the
union.
"""
#classmethod
def make_union(
mcs, *subenums: enum.EnumMeta, name: Union[str, Literal[AUTO], None] = AUTO
) -> enum.EnumMeta:
"""
Create an enum whose set of members is the union of members of several enums.
Order matters: where two members in the union have the same value, they will
be considered as aliases of each other, and the one appearing in the first
enum in the sequence will be used as the canonical members (the aliases will
be associated to this enum member).
:param subenums: Sequence of sub-enums to make a union of.
:param name: Name to use for the enum class. AUTO will result in a combination
of the names of all subenums, None will result in "UnionEnum".
:return: An enum class which is the union of the given subenums.
"""
subenums = mcs._normalize_subenums(subenums)
class UnionEnum(enum.Enum, metaclass=mcs):
pass
union_enum = UnionEnum
union_enum._subenums_ = subenums
if duplicate_names := reduce(
set.intersection, (set(subenum.__members__) for subenum in subenums)
):
raise ValueError(
f"Found duplicate member names in enum union: {duplicate_names}"
)
# If aliases are defined, the canonical member will be the one that appears
# first in the sequence of subenums.
# dict union keeps last key so we have to do it in reverse:
union_enum._value2member_map_ = value2member_map = reduce(
operator.or_, (subenum._value2member_map_ for subenum in reversed(subenums))
)
# union of the _member_map_'s but using the canonical member always:
union_enum._member_map_ = member_map = {
name: value2member_map[member.value]
for name, member in itt.chain.from_iterable(
subenum._member_map_.items() for subenum in subenums
)
}
# only include canonical aliases in _member_names_
union_enum._member_names_ = list(
mitt.unique_everseen(
itt.chain.from_iterable(subenum._member_names_ for subenum in subenums),
key=member_map.__getitem__,
)
)
if name is AUTO:
name = (
"".join(subenum.__name__.removesuffix("Enum") for subenum in subenums)
+ "UnionEnum"
)
UnionEnum.__name__ = name
elif name is not None:
UnionEnum.__name__ = name
return union_enum
def __repr__(cls):
return f"<union of {', '.join(map(str, cls._subenums_))}>"
def __instancecheck__(cls, instance):
return any(isinstance(instance, subenum) for subenum in cls._subenums_)
#classmethod
def _normalize_subenums(mcs, subenums):
"""Remove duplicate subenums and flatten nested unions"""
# we will need to collapse at most one level of nesting, with the inductive
# hypothesis that any previous unions are already flat
subenums = mitt.collapse(
(e._subenums_ if isinstance(e, mcs) else e for e in subenums),
base_type=enum.EnumMeta,
)
subenums = mitt.unique_everseen(subenums)
return tuple(subenums)
def enum_union(*enums, **kwargs):
return UnionEnumMeta.make_union(*enums, **kwargs)
Once we have that, we can just define the extend_enum decorator to compute the union of the base enum and the enum "extension", which will result in the desired behaviour:
def extend_enum(base_enum):
def decorator(extension_enum):
return enum_union(base_enum, extension_enum)
return decorator
Usage:
class GameNodeState(metaclass=abc.ABCMeta):
class Type(enum.Enum):
INIT = enum.auto()
INTERMEDIATE = enum.auto()
END = enum.auto()
class RoundState(GameNodeState):
#extend_enum(GameNodeState.Type)
class Type(enum.Enum):
TURN = GameNodeState.Type.INTERMEDIATE.value
class GameState(GameNodeState):
#extend_enum(GameNodeState.Type)
class Type(enum.Enum):
ROUND = GameNodeState.Type.INTERMEDIATE.value
Now all of the examples above produce the same output (plus the added instance check, i.e. isinstance(RoundState.Type.TURN, RoundState.Type) returns True).
I think this is a cleaner solution because it doesn't involve mucking around with descriptors; it doesn't need to know anything about the owner class (this works just as well with top-level classes).
Attribute lookup through subclasses and instances of GameNodeState should automatically link to the correct "extension" (i.e., union), as long as the extension enum is added with the same name as for the GameNodeState superclass so that it hides the original definition.
Original
Not sure how bad of an idea this is, but here is a solution using a descriptor wrapped around the enum that gets the set of aliases based on the class from which it is being accessed.
class ExtensibleClassEnum:
class ExtensionWrapperMeta(enum.EnumMeta):
#classmethod
def __prepare__(mcs, name, bases):
# noinspection PyTypeChecker
classdict: enum._EnumDict = super().__prepare__(name, bases)
classdict["_ignore_"] = ["base_descriptor", "extension_enum"]
return classdict
# noinspection PyProtectedMember
def __new__(mcs, cls, bases, classdict):
base_descriptor = classdict.pop("base_descriptor")
extension_enum = classdict.pop("extension_enum")
wrapper_enum = super().__new__(mcs, cls, bases, classdict)
wrapper_enum.base_descriptor = base_descriptor
wrapper_enum.extension_enum = extension_enum
base, extension = base_descriptor.base_enum, extension_enum
if set(base._member_map_.keys()) & set(extension._member_map_.keys()):
raise ValueError("Found duplicate names in extension")
# dict union keeps last key so we have to do it in reverse:
wrapper_enum._value2member_map_ = (
extension._value2member_map_ | base._value2member_map_
)
# union of both _member_map_'s but using the canonical member always:
wrapper_enum._member_map_ = {
name: wrapper_enum._value2member_map_[member.value]
for name, member in itertools.chain(
base._member_map_.items(), extension._member_map_.items()
)
}
# aliases shouldn't appear in _member_names_
wrapper_enum._member_names_ = list(
m.name for m in wrapper_enum._value2member_map_.values()
)
return wrapper_enum
def __repr__(self):
# have to use vars() to avoid triggering the descriptor
base_descriptor = vars(self)["base_descriptor"]
return (
f"<extension wrapper enum for {base_descriptor.base_enum}"
f" in {base_descriptor._extension2owner[self]}>"
)
def __init__(self, base_enum):
if not issubclass(base_enum, enum.Enum):
raise TypeError(base_enum)
self.base_enum = base_enum
# The user won't be able to retrieve the descriptor object itself, just
# the enum, so we have to forward calls to register_extension:
self.base_enum.register_extension = staticmethod(self.register_extension)
# mapping of owner class -> extension for subclasses that define an extension
self._extensions: Dict[Type, ExtensibleClassEnum.ExtensionWrapperMeta] = {}
# reverse mapping
self._extension2owner: Dict[ExtensibleClassEnum.ExtensionWrapperMeta, Type] = {}
# add the base enum as the base extension via __set_name__:
self._pending_extension = base_enum
#property
def base_owner(self):
# will be initialised after __set_name__ is called with base owner
return self._extension2owner[self.base_enum]
def __set_name__(self, owner, name):
# step 2 of register_extension: determine the class that defined it
self._extensions[owner] = self._pending_extension
self._extension2owner[self._pending_extension] = owner
del self._pending_extension
def __get__(self, instance, owner):
# Only compute extensions once:
if owner in self._extensions:
return self._extensions[owner]
# traverse in MRO until we find the closest supertype defining an extension
for supertype in owner.__mro__:
if supertype in self._extensions:
extension = self._extensions[supertype]
break
else:
raise TypeError(f"{owner} is not a subclass of {self.base_owner}")
# Cache the result
self._extensions[owner] = extension
return extension
def make_extension(self, extension: enum.EnumMeta):
class ExtensionWrapperEnum(
enum.Enum, metaclass=ExtensibleClassEnum.ExtensionWrapperMeta
):
base_descriptor = self
extension_enum = extension
return ExtensionWrapperEnum
def register_extension(self, extension_enum):
"""Decorator for enum extensions"""
# need a way to determine owner class
# add a temporary attribute that we will use when __set_name__ is called:
if hasattr(self, "_pending_extension"):
# __set_name__ not called after the previous call to register_extension
raise RuntimeError(
"An extension was created outside of a class definition",
self._pending_extension,
)
self._pending_extension = self.make_extension(extension_enum)
return self
Usage would be as follows:
class GameNodeState(metaclass=abc.ABCMeta):
#ExtensibleClassEnum
class Type(enum.Enum):
INIT = enum.auto()
INTERMEDIATE = enum.auto()
END = enum.auto()
class RoundState(GameNodeState):
#GameNodeState.Type.register_extension
class Type(enum.Enum):
TURN = GameNodeState.Type.INTERMEDIATE.value
class GameState(GameNodeState):
#GameNodeState.Type.register_extension
class Type(enum.Enum):
ROUND = GameNodeState.Type.INTERMEDIATE.value
Then:
>>> (RoundState.Type.TURN
... == RoundState.Type.INTERMEDIATE
... == GameNodeState.Type.INTERMEDIATE
... == GameState.Type.INTERMEDIATE
... == GameState.Type.ROUND)
...
True
>>> RoundState.Type.__members__
mappingproxy({'INIT': <Type.INIT: 1>,
'INTERMEDIATE': <Type.INTERMEDIATE: 2>,
'END': <Type.END: 3>,
'TURN': <Type.INTERMEDIATE: 2>})
>>> list(RoundState.Type)
[<Type.INTERMEDIATE: 2>, <Type.INIT: 1>, <Type.END: 3>]
>>> GameNodeState.Type.TURN
Traceback (most recent call last):
...
File "C:\Program Files\Python39\lib\enum.py", line 352, in __getattr__
raise AttributeError(name) from None
AttributeError: TURN
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')
I've been messing around with python's enum library and have come across a conundrum. In the docs, they show an example of an auto-numbering enum, wherein something is defined:
class Color(AutoNumber):
red = ()
green = ()
...
I want to make a similar class, but the value would automatically be set from the name of the member AND keep the functionality that you get from doing the str and enum mixin stuff
So something like:
class Animal(MagicStrEnum):
horse = ()
dog = ()
Animal.dog == 'dog' # True
I've looked at the source code of the enum module and tried a lot of variations messing around with __new__ and the EnumMeta class
Update: 2017-03-01
In Python 3.6 (and Aenum 2.01) Flag and IntFlag classes have been added; part of that was a new auto() helper that makes this trivially easy:
>>> class AutoName(Enum):
... def _generate_next_value_(name, start, count, last_values):
... return name
...
>>> class Ordinal(AutoName):
... NORTH = auto()
... SOUTH = auto()
... EAST = auto()
... WEST = auto()
...
>>> list(Ordinal)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]
Original answer
The difficulty with an AutoStr class is that the name of the enum member is not passed into the code that creates it, so it is unavailable for use. Another wrinkle is that str is immutable, so we can't change those types of enums after they have been created (by using a class decorator, for example).
The easiest thing to do is use the Functional API:
Animal = Enum('Animal', [(a, a) for a in ('horse', 'dog')], type=str)
which gives us:
>>> list(Animal)
[<Animal.horse: 'horse'>, <Animal.dog: 'dog'>]
>>> Animal.dog == 'dog'
True
The next easiest thing to do, assuming you want to make a base class for your future enumeration use, would be something like my DocEnem:
class DocEnum(Enum):
"""
compares equal to all cased versions of its name
accepts a doctring for each member
"""
def __new__(cls, *args):
"""Ignores arguments (will be handled in __init__)"""
obj = object.__new__(cls)
obj._value_ = None
return obj
def __init__(self, doc=None):
# first, fix _value_
self._value_ = self._name_.lower()
self.__doc__ = doc
def __eq__(self, other):
if isinstance(other, basestring):
return self._value_ == other.lower()
elif not isinstance(other, self.__class__):
return NotImplemented
return self is other
def __hash__(self):
# keep DocEnum hashable
return hash(self._value_)
def __ne__(self, other):
return not self == other
and in use:
class SpecKind(DocEnum):
REQUIRED = "required value"
OPTION = "single value per name"
MULTI = "multiple values per name (list form)"
FLAG = "boolean value per name"
KEYWORD = 'unknown options'
Note that unlike the first option, DocEnum members are not strs.
If you want to do it the hard way: subclass EnumMeta and fiddle with the new Enum's class dictionary before the members are created:
from enum import EnumMeta, Enum, _EnumDict
class StrEnumMeta(EnumMeta):
def __new__(metacls, cls, bases, oldclassdict):
"""
Scan through `oldclassdict` and convert any value that is a plain tuple
into a `str` of the name instead
"""
newclassdict = _EnumDict()
for k, v in oldclassdict.items():
if v == ():
v = k
newclassdict[k] = v
return super().__new__(metacls, cls, bases, newclassdict)
class AutoStrEnum(str, Enum, metaclass=StrEnumMeta):
"base class for name=value str enums"
class Animal(AutoStrEnum):
horse = ()
dog = ()
whale = ()
print(Animal.horse)
print(Animal.horse == 'horse')
print(Animal.horse.name, Animal.horse.value)
Which gives us:
Animal.horse
True
horse horse
1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.
Perhaps you are looking for the name attribute which is automatically provided by the Enum class
>>> class Animal(Enum):
... ant = 1
... bee = 2
... cat = 3
... dog = 4
...
>>> Animal.ant.name == "ant"
True
Though if you really want to shoot yourself in the foot. And I'm sure this will introduce a whole world of gotchas (I've eliminated the most obvious one).
from enum import Enum, EnumMeta, _EnumDict
class AutoStrEnumDict(_EnumDict):
def __setitem__(self, key, value):
super().__setitem__(key, key)
class AutoStrEnumMeta(EnumMeta):
#classmethod
def __prepare__(metacls, cls, bases):
return AutoStrEnumDict()
def __init__(self, name, bases, attrs):
super().__init__(name, bases, attrs)
# override Enum.__str__
# can't put these on the class directly otherwise EnumMeta overwrites them
# should also consider resetting __repr__, __format__ and __reduce_ex__
if self.__str__ is not str.__str__:
self.__str__ = str.__str__
class AutoStrNameEnum(str, Enum, metaclass=AutoStrEnumMeta):
pass
class Animal(AutoStrNameEnum):
horse = ()
dog = ()
print(Animal.horse)
assert Animal.horse == "horse"
assert str(Animal.horse) == "horse"
# and not equal to "Animal.horse" (the gotcha mentioned earlier)
While looking over some code in Think Complexity, I noticed their Graph class assigning values to itself. I've copied a few important lines from that class and written an example class, ObjectChild, that fails at this behavior.
class Graph(dict):
def __init__(self, vs=[], es=[]):
for v in vs:
self.add_vertex(v)
for e in es:
self.add_edge(e)
def add_edge(self, e):
v, w = e
self[v][w] = e
self[w][v] = e
def add_vertex(self, v):
self[v] = {}
class ObjectChild(object):
def __init__(self, name):
self['name'] = name
I'm sure the different built in types all have their own way of using this, but I'm not sure whether this is something I should try to build into my classes. Is it possible, and how? Is this something I shouldn't bother with, relying instead on simple composition, e.g. self.l = [1, 2, 3]? Should it be avoided outside built in types?
I ask because I was told "You should almost never inherit from the builtin python collections"; advice I'm hesitant to restrict myself to.
To clarify, I know that ObjectChild won't "work", and I could easily make it "work", but I'm curious about the inner workings of these built in types that makes their interface different from a child of object.
In Python 3 and later, just add these simple functions to your class:
class some_class(object):
def __setitem__(self, key, value):
setattr(self, key, value)
def __getitem__(self, key):
return getattr(self, key)
They are accomplishing this magic by inheriting from dict. A better way of doing this is to inherit from UserDict or the newer collections.MutableMapping
You could accomplish a similar result by doing the same:
import collections
class ObjectChild(collections.MutableMapping):
def __init__(self, name):
self['name'] = name
You can also define two special functions to make your class dictionary-like: __getitem__(self, key) and __setitem__(self, key, value). You can see an example of this at Dive Into Python - Special Class Methods.
Disclaimer : I might be wrong.
the notation :
self[something]
is legit in the Graph class because it inherits fro dict. This notation is from the dictionnaries ssyntax not from the class attribute declaration syntax.
Although all namespaces associated with a class are dictionnaries, in your class ChildObject, self isn't a dictionnary. Therefore you can't use that syntax.
Otoh, in your class Graph, self IS a dictionnary, since it is a graph, and all graphs are dictionnaries because they inherit from dict.
Is using something like this ok?
def mk_opts_dict(d):
''' mk_options_dict(dict) -> an instance of OptionsDict '''
class OptionsDict(object):
def __init__(self, d):
self.__dict__ = d
def __setitem__(self, key, value):
self.__dict__[key] = value
def __getitem__(self, key):
return self.__dict__[key]
return OptionsDict(d)
I realize this is an old post, but I was looking for some details around item assignment and stumbled upon the answers here. Ted's post wasn't completely wrong. To avoid inheritance from dict, you can make a class inherit from MutableMapping, and then provide methods for __setitem__ and __getitem__.
Additionally, the class will need to support methods for __delitem__, __iter__, __len__, and (optionally) other inherited mixin methods, like pop. The documentation has more info on the details.
from collections.abc import MutableMapping
class ItemAssign(MutableMapping):
def __init__(self, a, b):
self.a = a
self.b = b
def __setitem__(self, k, v):
setattr(self, k, v)
def __getitem__(self, k):
getattr(self, k)
def __len__(self):
return 2
def __delitem__(self, k):
self[k] = None
def __iter__(self):
yield self.a
yield self.b
Example use:
>>> x = ItemAssign("banana","apple")
>>> x["a"] = "orange"
>>> x.a
'orange'
>>> del x["a"]
>>> print(x.a)
None
>>> x.pop("b")
'apple'
>>> print(x.b)
None
Hope this serves to clarify how to properly implement item assignment for others stumbling across this post :)
Your ObjectChild doesn't work because it's not a subclass of dict. Either of these would work:
class ObjectChild(dict):
def __init__(self, name):
self['name'] = name
or
class ObjectChild(object):
def __init__(self, name):
self.name = name
You don't need to inherit from dict. If you provide setitem and getitem methods, you also get the desired behavior I believe.
class a(object):
def __setitem__(self, k, v):
self._data[k] = v
def __getitem__(self, k):
return self._data[k]
_data = {}
Little memo about <dict> inheritance
For those who want to inherit dict.
In this case MyDict will have a shallow copy of original dict in it.
class MyDict(dict):
...
d = {'a': 1}
md = MyDict(d)
print(d['a']) # 1
print(md['a']) # 1
md['a'] = 'new'
print(d['a']) # 1
print(md['a']) # new
This could lead to problem when you have a tree of nested dicts and you want to covert part of it to an object. Changing this object will not affect its parent
root = {
'obj': {
'a': 1,
'd': {'x': True}
}
}
obj = MyDict(root['obj'])
obj['a'] = 2
print(root) # {'obj': {'a': 1, 'd': {'x': True}}} # 'a' is the same
obj['d']['x'] = False
print(root) # {'obj': {'a': 1, 'd': {'x': True}}} # 'x' chanded