python 3.2 plugin factory: instantiation from class/metaclass
Asked Answered
L

1

13

I'm riffing from the information here: Metaclass not being called in subclasses

My problem is that I'm unable to create an instance of an object using this class registry. If I use "regular" construction methods, then it seems to instantiate objects correctly; but when I try to use the class object associated with registry, then I get an error that I'm passing an incorrect number of arguments. (Seems to be calling the metaclass new and not my constructor...??)

I'm not clear why it's failing, because I thought I should be able to create an instance from the class object by using "callable" syntax.

Seems I'm getting the metaclass put in the registry and not the class itself? But I don't see an easy way to access the class itself in the new call.

Here is my code example, which fails to instantiate a variable 'd':

registry = [] # list of subclasses

class PluginMetaclass(type):
    def __new__(cls, name, bases, attrs):
        print(cls)
        print(name)
        registry.append((name, cls))
        return super(PluginMetaclass, cls).__new__(cls, name, bases, attrs)

class Plugin(metaclass=PluginMetaclass):
    def __init__(self, stuff):
        self.stuff = stuff

# in your plugin modules
class SpamPlugin(Plugin):
    def __init__(self, stuff):
        self.stuff = stuff

class BaconPlugin(Plugin):
    def __init__(self, stuff):
        self.stuff = stuff

c = SpamPlugin(0)
b = BaconPlugin(0)
mycls = registry[1][1]
d = mycls(0)

Thanks for any help.

Legalism answered 9/5, 2011 at 18:17 Comment(0)
I
6

I think the issue you're having is that the cls parameter passed to a metaclass constructor is actually a reference to the metaclass and not the class which is being created. Since __new__ is a classmethod of PluginMetaclass, it's associated with that class just like any regular classmethod. You probably want to be registering the newly created class object you're getting from super(PluginMetaclass, cls).__new__(..).

This modified version worked for me on 3.2:

class PluginMetaclass(type):
    def __new__(cls, name, bases, attrs):
        print("Called metaclass: %r" % cls)
        print("Creating class with name: %r" % name)
        newclass = super(PluginMetaclass, cls).__new__(cls, name, bases, attrs)
        print("Registering class: %r" % newclass)
        registry.append((name, newclass))
        return newclass

and the print() calls show what's going on behind the scenes:

>>> registry = []
>>>
>>> class Plugin(metaclass=PluginMetaclass):
...     def __init__(self, stuff):
...         self.stuff = stuff
...
Called metaclass: <class '__main__.PluginMetaclass'>
Creating class with name: 'Plugin'
Registering class: <class '__main__.Plugin'>
>>> class SpamPlugin(Plugin):
...     def __init__(self, stuff):
...         self.stuff = stuff
...
Called metaclass: <class '__main__.PluginMetaclass'>
Creating class with name: 'SpamPlugin'
Registering class: <class '__main__.SpamPlugin'>
>>> class BaconPlugin(Plugin):
...     def __init__(self, stuff):
...         self.stuff = stuff
...
Called metaclass: <class '__main__.PluginMetaclass'>
Creating class with name: 'BaconPlugin'
Registering class: <class '__main__.BaconPlugin'>
>>> c = SpamPlugin(0)
>>> b = BaconPlugin(0)
>>> mycls = registry[1][1]
>>> d = mycls(0)
>>> d
<__main__.SpamPlugin object at 0x010478D0>
>>> registry
[('Plugin', <class '__main__.Plugin'>), 
('SpamPlugin', <class '__main__.SpamPlugin'>), 
('BaconPlugin', <class '__main__.BaconPlugin'>)]

Edit: @drone115b also solved this by using __init__ instead of __new__ in PluginMetaclass. That's probably the better way to go in most cases.

Ichthyornis answered 9/5, 2011 at 21:58 Comment(3)
Yes, Greg, thanks. I guess-and-tested for several hours yesterday before happening on an even simpler solution. Replace new with init and the first parameter is the class name instead of the metaclass name. The call to super.__init__ will need to have the first argument removed but otherwise it's a fix to the original code that involves changing only the new to init. I certainly wish the docs were clearer on the available interface for metaclasses.Legalism
I should also say, I have a slight personal preference to use init rather than new. Init is a validation whereas new is an allocation. I don't want to be messing around at a lower level of detail than necessary and I perceive class registration to be more of a validation step. Either way definitely works, though.Legalism
I agree, __init__ feels better to me as well, and is definitely more appropriate for this application. I think the need for __new__ is fairly rare in general, and especially rare for metaclasses.Ichthyornis

© 2022 - 2024 — McMap. All rights reserved.