I have an enumerated descriptor which I want to be able to add to classes dynamically. Here it is
class TypedEnum:
'''Class to make setting of enum types valid and error checked'''
def __init__(self, enum, default=None):
self._enum = enum
self._enum_by_value = {e.value: e for e in enum}
self._enum_by_name = {e.name: e for e in enum}
self._value = next(iter(enum))
if default is not None:
self.value = default
@property
def value(self):
return self._value.value
@value.setter
def value(self, value):
if value in self._enum:
value = getattr(self._enum, value.name)
elif value in self._enum_by_name:
value = self._enum_by_name[value]
elif value in self._enum_by_value:
value = self._enum_by_value[value]
else:
raise ValueError("Value does not exist in enum: {}".format(value))
self._value = value
@property
def name(self):
return self._value.name
def __str__(self):
return repr(self._value.name)
def __get__(self, obj, type=None):
return self._value.name
def __set__(self, obj, value):
self.value = value
To try and understand this behavior, I wrote up a unit test that goes over it
from enum import Enum
from unittest import TestCase
class abc(Enum):
a = 0
b = 1
c = 2
def test_descriptor():
'''test mostly the features of __get__ and __set__
Basically when they are just objects they are fair game,
but when they become a member of a class they are super
protected (I can't even figure out a way to get access to
the base object)'''
venum = TypedEnum(abc)
assert isinstance(venum, TypedEnum)
# set works normally when not a member of an object
venum = 0
assert isinstance(venum, int)
venum = TypedEnum(abc)
x = venum
assert isinstance(x, TypedEnum)
# But when it is a member of a class/object, it is hidden
class C:
e = venum
c = C()
assert c.e == 'a' and isinstance(c.e, str)
c.e = 'b'
assert c.e == 'b' and isinstance(c.e, str)
TestCase.assertRaises(None, ValueError, setattr, c, 'e', 'z')
# However, when it is added on in init it doesn't behave the same
# way
class C:
def __init__(self):
self.e = venum
c = C()
# This would not have been true before
assert c.e != 'a' and isinstance(c.e, TypedEnum)
# to implement this, it seems we need to overload the getattribute
class D(C):
def __getattribute__(self, attr):
obj = object.__getattribute__(self, attr)
if hasattr(obj, '__get__'):
print('getting __get__', obj, attr)
return obj.__get__(self)
else:
return obj
def __setattr__(self, attr, value):
if not hasattr(self, attr):
object.__setattr__(self, attr, value)
return
obj = object.__getattribute__(self, attr)
if hasattr(obj, '__set__'):
print('setting __set__', obj, attr, value)
obj.__set__(self, value)
else:
setattr(self, attr, value)
c = D()
c.e = 'b'
assert c.e == 'b' and isinstance(c.e, str)
TestCase.assertRaises(None, ValueError, setattr, c, 'e', 'z')
test_descriptor()
Is this all sane? Why the heck does it work differently whether it is a member of __dict__
or class.__dict__
???
Please tell me there is a better way to do this!