Issue
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__()
...
Solution
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:
- the
class Dep_A
statement is found, and the class body is executed to form the attributes (creating the__init__
function). - the metaclass
__new__
method is called with the classname, bases, and the class body namespace ('Dep_A'
,(Department,)
and{'__init__': <function ...>, ...}
respectively). - the metaclass creates the new class object
- The metaclass calls
registry()
, passing in the new class object registry()
calls the class object- The class
__init__
method is called
Normally, 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'>}}
Answered By - Martijn Pieters
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.