类装饰器、继承、super()与最大递归
我正在尝试弄明白如何在使用 super()
的子类上使用装饰器。因为我的类装饰器会创建另一个子类,所以当它改变传给 super(className, self)
的 className
时,似乎会导致装饰过的类无法使用 super()
。下面是一个例子:
def class_decorator(cls):
class _DecoratedClass(cls):
def __init__(self):
return super(_DecoratedClass, self).__init__()
return _DecoratedClass
class BaseClass(object):
def __init__(self):
print "class: %s" % self.__class__.__name__
def print_class(self):
print "class: %s" % self.__class__.__name__
bc = BaseClass().print_class()
class SubClass(BaseClass):
def print_class(self):
super(SubClass, self).print_class()
sc = SubClass().print_class()
@class_decorator
class SubClassAgain(BaseClass):
def print_class(self):
super(SubClassAgain, self).print_class()
sca = SubClassAgain()
# sca.print_class() # Uncomment for maximum recursion
输出应该是:
class: BaseClass
class: BaseClass
class: SubClass
class: SubClass
class: _DecoratedClass
Traceback (most recent call last):
File "class_decorator_super.py", line 34, in <module>
sca.print_class()
File "class_decorator_super.py", line 31, in print_class
super(SubClassAgain, self).print_class()
...
...
RuntimeError: maximum recursion depth exceeded while calling a Python object
有没有人知道在使用装饰器时,如何不破坏使用 super()
的子类?理想情况下,我希望能不时重用一个类,并简单地给它加上装饰,而不影响它的功能。
5 个回答
你可能已经知道,问题出在名字 SubClassAgain
在 SubClassAgain.print_class
中是局限于当前模块的全局命名空间的。因此,SubClassAgain
实际上指的是类 _DecoratedClass
,而不是被装饰的那个类。要获取被装饰的类,可以遵循一个约定,就是类装饰器有一个属性指向被装饰的类。
def class_decorator(cls):
class _DecoratedClass(cls):
original=cls
def __init__(self):
print '_DecoratedClass.__init__'
return super(_DecoratedClass, self).__init__()
return _DecoratedClass
@class_decorator
class SubClassAgain(BaseClass):
original
def print_class(self):
super(self.__class__.original, self).print_class()
另一种方法是使用 __bases__
属性来获取被装饰的类。
@class_decorator
class SubClassAgain(BaseClass):
def print_class(self):
super(self.__class__.__bases__[0], self).print_class()
当然,如果有多个装饰器,这两种方法都变得很麻烦。而且后者在处理被装饰类的子类时也不管用。你可以结合装饰器和混入(mixin),写一个装饰器把混入添加到一个类中。不过,这样做并不能帮助你重写方法。
def class_decorator(cls):
class _DecoratedClass(object):
def foo(self):
return 'foo'
cls.__bases__ += (_DecoratedClass, )
return cls
最后,你可以直接操作类的属性来设置方法。
def class_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
对于你的例子来说,这可能是最好的选择,尽管基于混入的装饰器也有它的用处。
基本上,你可以在交互式Python提示符下输入你的代码示例后看到问题:
>>> SubClassAgain
<class '__main__._DecoratedClass'>
也就是说,名字 SubClassAgain
现在被绑定(在全局范围内,在这种情况下)到一个类,这个类实际上不是“真正的” SubClassAgain
,而是它的一个子类。所以,任何对这个名字的晚绑定引用,比如你在 super(SubClassAgain,
调用中的那个,当然会得到那个假装成这个名字的子类——那个子类的父类当然是“真正的 SubClassAgain
”,因此导致了无限递归。
你可以非常简单地重现同样的问题,只需让任何子类夺取其基类的名字:
>>> class Base(object):
... def pcl(self): print 'cl: %s' % self.__class__.__name__
...
>>> class Sub(Base):
... def pcl(self): super(Sub, self).pcl()
...
>>> Sub().pcl()
cl: Sub
>>> class Sub(Sub): pass
...
现在, Sub().pcl()
将导致无限递归,因为“名字夺取”。类装饰,除非你用它来装饰并返回你作为参数得到的同一个类,否则就是系统性的“名字夺取”,因此与那些必须返回该名字的“真实”类的用法不兼容,而不是夺取者(无论是在 self
中还是其他地方)。
解决方法——如果你绝对需要同时有类装饰作为夺取(不仅仅是通过改变接收到的类参数进行的类装饰),并且 super
——基本上需要一些协议来协调夺取者和可能被夺取者之间的合作,比如对你的示例代码进行以下小改动:
def class_decorator(cls):
class _DecoratedClass(cls):
_thesuper = cls
def __init__(self):
return super(_DecoratedClass, self).__init__()
return _DecoratedClass
...
@class_decorator
class SubClassAgain(BaseClass):
def print_class(self):
cls = SubClassAgain
if '_thesuper' in cls.__dict__:
cls = cls._thesuper
super(cls, self).print_class()
这个装饰器会导致一种类似于“钻石继承”的问题。为了避免这些麻烦,你可以不使用 super()
。把 SubClassAgain
改成下面这样,就可以防止无限递归的问题:
@class_decorator
class SubClassAgain(BaseClass):
def print_class(self):
BaseClass.print_class(self)