Reputation: 25
I am trying to use automatic registration of subclasses using a metaclass,
but the below code throws an error in class Dep_A on the line
super(Dep_A,self).__init__().
*NameError: global name 'Dep_A' is not defined*
Tried the commented line in MetaClass, but with the same error. I know this works when not using a metaclass so how can I initialize the parent class's init method
registry = {}
def register(cls):
print cls.__name__
registry[cls.__name__] = cls()
return cls
class MetaClass(type):
def __new__(mcs, clsname, bases, attrs):
# newclass = super(MetaClass, mcs).__new__(mcs, clsname, bases, attrs)
newclass = type.__new__(mcs,clsname, bases, attrs)
register(newclass)
return newclass
class Department(object):
__metaclass__ = MetaClass
def __init__(self):
self.tasks = dict()
class Dep_A(Department):
def __init__(self):
super(Dep_A,self).__init__()
...
Upvotes: 0
Views: 360
Reputation: 1122222
You are trying to create an instance of the class before the class
statement has completed. The class object has been created, but has not yet been assigned to a global name.
The order of events is:
class Dep_A
statement is found, and the class body is executed to form the attributes (creating the __init__
function).__new__
method is called with the classname, bases, and the class body namespace ('Dep_A'
, (Department,)
and {'__init__': <function ...>, ...}
respectively).registry()
, passing in the new class objectregistry()
calls the class object__init__
method is calledNormally, Dep_A
is assigned when the metaclass __new__
method returns the newly created object. Until that point there is no global name Dep_A
assigned, so the super(Dep_A, self).__init__()
call fails.
You could set the name from there first:
class MetaClass(type):
def __new__(mcs, clsname, bases, attrs):
# newclass = super(MetaClass, mcs).__new__(mcs, clsname, bases, attrs)
newclass = type.__new__(mcs,clsname, bases, attrs)
globals()[clsname] = newclass
register(newclass)
return newclass
Alternatively, don't try to create an instance in the registry, store a class instead. You can always create a single instance later on.
The following implements a registry that transparently creates an instance later when you need one, and stores just that one instance created:
from collections import Mapping
class Registry(Mapping):
def __init__(self):
self._classes = {}
self._instances = {}
def __getitem__(self, name):
if name not in self._instances:
self._instances[name] = self._classes.pop(name)()
return self._instances[name]
def __iter__(self):
return iter(self._classes.viewkeys() | self._instances.viewkeys())
def __len__(self):
return len(self._classes) + len(self._instances)
def register(self, cls):
self._classes[cls.__name__] = cls
registry = Registry()
class MetaClass(type):
def __new__(mcs, clsname, bases, attrs):
# newclass = super(MetaClass, mcs).__new__(mcs, clsname, bases, attrs)
newclass = type.__new__(mcs,clsname, bases, attrs)
registry.register(newclass)
return newclass
Now classes are stored in a separate mapping and only when you then later look up the class from the registry mapping will an instance be created and cached:
>>> class Department(object):
... __metaclass__ = MetaClass
... def __init__(self):
... self.tasks = dict()
...
>>> class Dep_A(Department):
... def __init__(self):
... super(Dep_A,self).__init__()
...
>>> registry.keys()
['Department', 'Dep_A']
>>> registry['Dep_A']
<__main__.Dep_A object at 0x110536ad0>
>>> vars(registry)
{'_instances': {'Dep_A': <__main__.Dep_A object at 0x110536ad0>}, '_classes': {'Department': <class '__main__.Department'>}}
Upvotes: 3
Reputation: 531265
Your register
function is trying to instantiate Dep_A
before the global name is actually defined. (That is, it gets called while the body of the class statement is still being executed, before Dep_A
is assigned the result of the call to the metaclass.)
You want to store the class itself instead:
def register(cls):
registry[cls.__name__] = cls # Not cls()
return cls
Upvotes: 1