I watched a great video on YouTube about Python metaprogramming. I tried to write the following code (which is almost the same from the video):
class Descriptor:
def __init__(self, name):
self.name = name
def __get__(self, instance, cls):
return instance.__dict__[self.name]
def __set__(self, instance, val):
instance.__dict__[self.name] = val
def __delete__(self, instance):
del instance.__dict__[self.name]
class Type(Descriptor):
ty = object
def __set__(self, instance, val):
if not isinstance(val, self.ty):
raise TypeError("%s should be of type %s" % (self.name, self.ty))
super().__set__(instance, val)
class String(Type):
ty = str
class Integer(Type):
ty = int
class Positive(Descriptor):
def __set__(self, instance, val):
if val <= 0:
raise ValueError("Must be > 0")
super().__set__(instance, val)
class PositiveInteger(Integer, Positive):
pass
class Person(metaclass=StructMeta):
_fields = ['name', 'gender', 'age']
name = String('name')
gender = String('gender')
age = PositiveInteger('age')
So PositiveInteger
is inherited from Integer
and Positive
, and both classes have __get__
method defined to do some validation. I wrote some test code to convince myself that both methods will run:
class A:
def test(self):
self.a = 'OK'
class B:
def test(self):
self.b = 'OK'
class C(A, B):
pass
c = C()
c.test()
print(self.a)
print(self.b)
Only to find that only the first print statement works. The second will raise an AttributeError, which indicates that when there's name conflict, the first base class wins.
So I wonder why both validations work? It's even more weird that when only the Integer check passes (e.g. person.age = -3), it's super().__set__(instance, val)
has no effect, leaving person.age untouched.