Class Decorators, Inheritance, Super(), And Maximum Recursion
Solution 1:
Basically, you can see the problem after entering your code sample at the interactive Python prompt:
>>> SubClassAgain
<class'__main__._DecoratedClass'>
i.e., the name SubClassAgain
is now bound (in global scope, in this case) to a class that in fact isn't the "real" SubClassAgain
, but a subclass thereof. So, any late-bound reference to that name, like the one you have in its super(SubClassAgain,
call, will of course get the subclass that's masquerading by that name -- that subclass's superclass is of course "the real SubClassAgain
", whence the infinite recursion.
You can reproduce the same problem very simply without decoration, just by having any subclass usurp its base-class's name:
>>>classBase(object):...defpcl(self): print'cl: %s' % self.__class__.__name__...>>>classSub(Base):...defpcl(self): super(Sub, self).pcl()...>>>Sub().pcl()
cl: Sub
>>>classSub(Sub): pass...
now, Sub().pcl()
will cause infinite recursion, due to the "name usurpation". Class decoration, unless you use it to decorate and return the same class you get as an argument, is systematic "name usurpation", and thus incompatible with uses of the class name which absolutely must return the "true" class of that name, and not the usurper (be that in self
or otherwise).
Workarounds -- if you absolutely must have both class decoration as usurpation (not just class decoration by changes in the received class argument), andsuper
-- basically need protocols for cooperation between the usurper and the possible-usurpee, such as the following small changes to your example code:
defclass_decorator(cls):
class_DecoratedClass(cls):
_thesuper = cls
def__init__(self):
returnsuper(_DecoratedClass, self).__init__()
return _DecoratedClass
...
@class_decoratorclassSubClassAgain(BaseClass):defprint_class(self):
cls = SubClassAgain
if'_thesuper'in cls.__dict__:
cls = cls._thesuper
super(cls, self).print_class()
Solution 2:
The decorator creates a kind-of diamond inheritance situation. You can avoid these problems by not using super()
. Changing SubClassAgain
to the following will prevent infinite recursion:
@class_decoratorclassSubClassAgain(BaseClass):defprint_class(self):
BaseClass.print_class(self)
Solution 3:
As you might already be aware, the problem arises from the fact that the name SubClassAgain
in SubClassAgain.print_class
is scoped to the current module's global namespace. SubClassAgain
thus refers to the class _DecoratedClass
rather than the class that gets decorated. One way of getting at the decorated class is to follow a convention that class decorators have a property referring to the decorated class.
defclass_decorator(cls):
class_DecoratedClass(cls):
original=cls
def__init__(self):
print '_DecoratedClass.__init__'returnsuper(_DecoratedClass, self).__init__()
return _DecoratedClass
@class_decoratorclassSubClassAgain(BaseClass):
original
defprint_class(self):
super(self.__class__.original, self).print_class()
Another is to use the __bases__
property to get the decorated class.
@class_decoratorclassSubClassAgain(BaseClass):defprint_class(self):
super(self.__class__.__bases__[0], self).print_class()
Of course, with multiple decorators, either of these become unwieldy. The latter also doesn't work with subclasses of the decorated class. You can combine decorators and mixins, writing a decorator that adds a mixin into a class. This won't help you override methods.
defclass_decorator(cls):
class_DecoratedClass(object):deffoo(self):
return'foo'
cls.__bases__ += (_DecoratedClass, )
return cls
Lastly, you can work directly with the class attributes to set methods.
defclass_decorator(cls):
old_init = getattr(cls, '__init__')
def__init__(self, *args, **kwargs):
print'decorated __init__'
old_init(self, *args, **kwargs)
setattr(cls, '__init__', __init__)
return cls
This is probably the best option for your example, though the mixin-based decorator also has its uses.
Solution 4:
Are you sure you want to use a class decorator and not simply inheritance? For instance, instead of a decorator to replace your class with a subclass introducing some methods, perhaps you want a mixin class and to use multiple inheritance to create the final class?
This would be accomplished by something like
classMyMixIn(object):def__init__(self):
super(MyMixIn, self).__init__()
classBaseClass(object):def__init__(self):
print "class: %s" % self.__class__.__name__
defprint_class(self):
print "class: %s" % self.__class__.__name__
classSubClassAgain(BaseClass, MyMixIn):defprint_class(self):
super(SubClassAgain, self).print_class()
sca = SubClassAgain()
sca.print_class()
Solution 5:
How about simply promoting _DecoratedClass
's __bases__
up to the __bases__
of SubClassAgain
?
defclass_decorator(cls):
class_DecoratedClass(cls):def__init__(self):
returnsuper(_DecoratedClass, self).__init__()
_DecoratedClass.__bases__=cls.__bases__
return _DecoratedClass
Post a Comment for "Class Decorators, Inheritance, Super(), And Maximum Recursion"