Thomas Junk Thomas Junk - 21 days ago 9
Python Question

Overwritten __repr__ after instance creation

I am playing a little bit around with Python metaprogramming.

class FormMetaClass(type):

def __new__(cls, clsname, bases, methods):
# Attach attribute names to the descriptors
for key, value in methods.items():
if isinstance(value, FieldDescriptor):
value.name = key
return type.__new__(cls, clsname, bases, methods)


class Form(metaclass=FormMetaClass):

@classmethod
def from_json(cls, incoming):
instance = cls()
data = json.loads(incoming)
for k, v in data.items():
if (not hasattr(instance, k)):
raise KeyError("Atrribute not found")
instance.__setattr__(k, v)
return cls

class MyForm(Form):

first_name = String()
last_name = String()
age = Integer()

def __repr__(self):
return "{} {}".format(self.first_name, self.last_name)


def main():
data = json.dumps({'first_name': 'Thomas',
'last_name': 'Junk'})
form = MyForm.from_json(data)
print(form)

if __name__ == "__main__":
main()

class FieldDescriptor:

def __init__(self, name=None, **opts):
self.name = name
for key, value in opts.items():
setattr(self, key, value)

def __set__(self, instance, value):
instance.__dict__[self.name] = value


class Typechecked(FieldDescriptor):
expected_type = type(None)

def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError('expected ' + str(self.expected_type))
super().__set__(instance, value)


class Integer(Typechecked):
expected_type = int


class String(Typechecked):
expected_type = str


I have a
Form
which has a metaclass
FormMetaClass
.
To have an alternative constructor I am using a
@classmethod
.
I create an
instance
, which seem to work so far.

What doesn't work is calling the
__repr__
(or
__str__
interchangeably).
When I create an instance via
MyForm()
everything is fine.
When I create an instance via the
@classmethod
, some "default" implementation is taken.

I expected
Thomas Junk
, but I get
<class '__main__.MyForm'>


Could you give me a hint, what I am overlooking?

Answer

You are returning the class, not the newly created instance:

return cls

So you return MyForm, not the new instance MyForm() you just set all the attributes on. And you indeed see the repr() output for the class:

>>> form is MyForm
True
>>> print(MyForm)
<class '__main__.MyForm'>

The fix is simple, return instance instead:

return instance

or, as a full method:

@classmethod
def from_json(cls, incoming):
    instance = cls()
    data = json.loads(incoming)
    for k, v in data.items():
        if (not hasattr(instance, k)):
            raise KeyError("Atrribute not found")
        instance.__setattr__(k, v)
    return instance

at which point the method returns an instance and everything works:

>>> isinstance(form, MyForm)
True
>>> print(form)
Thomas Junk
Comments