【问题标题】:Class Decorators, Inheritance, super(), and maximum recursion类装饰器、继承、super() 和最大递归
【发布时间】:2011-02-02 07:40:18
【问题描述】:

我试图弄清楚如何在使用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() 的子类的方法?理想情况下,我想不时重用一个类,并简单地装饰它而不破坏它。

【问题讨论】:

    标签: python decorator


    【解决方案1】:

    简单地将_DecoratedClass__bases__ 提升到SubClassAgain__bases__ 怎么样?

    def class_decorator(cls):
        class _DecoratedClass(cls):
            def __init__(self):
                return super(_DecoratedClass, self).__init__()
        _DecoratedClass.__bases__=cls.__bases__
        return _DecoratedClass
    

    【讨论】:

      【解决方案2】:

      装饰器创建了一种钻石继承的情况。您可以通过不使用super() 来避免这些问题。将SubClassAgain 更改为以下内容将防止无限递归:

      @class_decorator
      class SubClassAgain(BaseClass):
          def print_class(self):
              BaseClass.print_class(self)
      

      【讨论】:

      • 感谢您的回复!我选择了这个答案,因为它没有将子类与装饰器耦合。我意识到我的问题是关于如何使用 super(),但我想答案不是。
      【解决方案3】:

      您可能已经知道,问题出在SubClassAgain.print_class 中的名称SubClassAgain 的作用域为当前模块的全局命名空间。 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()
      

      当然,如果有多个装饰器,其中任何一个都会变得笨拙。后者也不适用于装饰类的子类。你可以结合装饰器和混合器,编写一个装饰器,将混合器添加到一个类中。这不会帮助您覆盖方法。

      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
      

      这可能是您的示例的最佳选择,尽管基于 mixin 的装饰器也有其用途。

      【讨论】:

        【解决方案4】:

        基本上,您可以在交互式 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()
        

        【讨论】:

          【解决方案5】:

          您确定要使用类装饰器而不是简单的继承吗?例如,不是使用装饰器将您的类替换为引入一些方法的子类,也许您想要一个 mixin 类并使用多重继承来创建最终类?

          这将通过类似的方式来完成

          class MyMixIn(object):
              def __init__(self):
                  super(MyMixIn, self).__init__()
          
          class BaseClass(object):
              def __init__(self):
                  print "class: %s" % self.__class__.__name__
              def print_class(self):
                  print "class: %s" % self.__class__.__name__
          
          class SubClassAgain(BaseClass, MyMixIn):
              def print_class(self):
                  super(SubClassAgain, self).print_class()
          
          sca = SubClassAgain()
          sca.print_class() 
          

          【讨论】:

          • 我真的很喜欢这个解决方案。不过,我使用了一些带有参数(权限)的类装饰器。我可以使用 mix-in 来更改 init 参数,但我认为我更喜欢在类中添加 @decorator(perm) 的灵活性,而不是在我想初始化时更改我的代码具有权限参数的类。不过我可以用它代替其他装饰器!
          • 由于元类的存在,从技术上讲,您可以将类装饰器用于任何东西的混入。一般来说,如果我想向一个类添加方法,我会使用 mixins,而当我根本不想改变一个类,而只是注册它或类似的东西时,我会使用类装饰器。
          • 谢谢,迈克!这听起来像是一个很好的方法。我如何将权限传递给 mixin?当我声明它时,我会使其成为子类的属性吗?
          猜你喜欢
          • 2011-03-23
          • 2011-03-01
          • 2014-03-07
          • 2023-04-09
          • 2011-03-01
          • 2012-11-14
          • 1970-01-01
          • 2011-11-20
          • 2018-09-30
          相关资源
          最近更新 更多