I am trying to create a FractionEnum similar to StrEnum or IntEnum. My first attempt resulted in a metaclass conflict:
class FractionEnum(fractions.Fraction, Enum):
VALUE_1 = 1, 1
VALUE_2 = 8, 9
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I followed the suggestion from this answer Multiple inheritance metaclass conflict involving Enum and created a new metaclass:
class FractionEnumMeta(type(Enum), type(fractions.Fraction)):
pass
class FractionEnum(fractions.Fraction, Enum, metaclass=FractionEnumMeta):
VALUE_1 = 1, 1
VALUE_2 = 8, 9
This solved the above error but now I get:
File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/enum.py", line 289, in __new__
enum_member = __new__(enum_class, *args)
File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/fractions.py", line 93, in __new__
self = super(Fraction, cls).__new__(cls)
TypeError: Enum.__new__() missing 1 required positional argument: 'value'
The issue seems to be that the __new__
call inside Fraction is trying to create an enum, from the call inside the EnumMeta metaclass:
else:
enum_member = __new__(enum_class, *args)
I'm misunderstanding how the metaclasses can work together to create an object that is both a fraction and an Enum - it seems to work out of the box with int or str or classes that don't define a metaclass.
Update:
I was able to use the code below to have the enumeration replace the Fraction's new method, but I am getting an error if I try deepcopy a class that has the enum as a member:
/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/enum.py:497: in _create_
_, first_enum = cls._get_mixins_(cls, bases)
in the Enum code:
# ensure final parent class is an Enum derivative, find any concrete
# data type, and check that Enum has no members
first_enum = bases[-1]
if not issubclass(first_enum, Enum):
raise TypeError("new enumerations should be created as "
"`EnumName([mixin_type, ...] [data_type,] enum_type)`")
member_type = _find_data_type(bases) or object
if first_enum._member_names_:
> raise TypeError("Cannot extend enumerations")
E TypeError: Cannot extend enumerations
Sample to reproduce:
class TestFractionEnum(FractionEnum):
VALUE_1 = 1, 1
VALUE_2 = 8, 9
class C:
def __init__(self):
self.fraction_enum = TestFractionEnum.VALUE_1
c = C()
print(c)
print(c.fraction_enum)
d = copy.copy(c)
print(d)
e = copy.deepcopy(c)
print(e)
Update 2:
Overriding deepcopy on the enum seems to work:
def __deepcopy__(self, memo):
if type(self) == Fraction:
return self
for item in self.__class__:
if self == item:
return item
assert f'Invalid enum: {self}'