Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redefining __init__ with metaclass in Python 3

I'm learning meta programming in python 3. My professor did give us this exercise:

Write the metaclass Spell that transforms the instances of Student in a Wizard.

I did this:

class Spell(type):
    def __new__(cls, classname, supers, cls_dict):
        cls_dict['__init__'] = Spell.toWizard()
        cls_dict['__qualname__'] = 'wizard'
        return type.__new__(cls, 'wizard', supers, cls_dict)

    @staticmethod
    def toWizard():
        def init(*args):
            wizard(args, 'fire_spell')
        return init    

class student(metaclass=Spell):
    def __init__(self, name):
        self.name = name

class person():
    def __init__(self, name):
        self.name = name

class wizard(person):
    def __init__(self, name, magic):
        self.magic = magic
        super().__init__(name)



if __name__ == '__main__':
    s = student('name1')
    print(s.__dict__)
    s = student('name2')
    print(s.__dict__)

The wizard class __init__ is correctly being called instead of student class __init__, however the objects created have an empty __dict__. Where am I going wrong?

like image 486
DarthRayban Avatar asked Mar 07 '23 21:03

DarthRayban


1 Answers

Your init() replacement function creates a local wizard() instance, and doesn't return anything:

def init(*args):
    wizard(args, 'fire_spell')

That's a separate instance, self is not touched.

Don't use __new__; that produces a new class. You only renamed the student class and gave it an ineffectual __init__ method.

Override the __call__ method to hook into creating the instances. There you can replace or ignore the first argument, which is the student class object and use the wizard class in its place:

class Spell(type):
    def __call__(cls, *args, **kwargs):
        # ignore the cls argument, return a wizard instead
        if 'magic' not in kwargs and len(args) < 2:
            # provide a default magic value
            kwargs['magic'] = 'fire_spell'
        return wizard(*args, **kwargs)

Because student only takes a single argument normally, so the above adds a magic argument if none is specified.

Demo:

>>> student('name1')
<__main__.wizard object at 0x10f1e8198>
>>> vars(_)
{'magic': 'fire_spell', 'name': 'name1'}
like image 137
Martijn Pieters Avatar answered Mar 20 '23 09:03

Martijn Pieters