Декораторы класса, Наследование, супер (), и максимальная рекурсия

Я пытаюсь выяснить, как использовать декораторов на подклассах то использование super(). Так как мой декоратор класса создает другой подкласс, украшенный класс, кажется, предотвращает использование super() когда это изменяется className переданный super(className, self). Ниже пример:

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() при использовании декоратора? Идеально я хотел бы снова использовать класс время от времени и просто украсить его w/out повреждение его.

7
задан efotinis 19 November 2010 в 21:38
поделиться

5 ответов

Декоратор создает ситуацию наследования типа ромба. Вы можете избежать этих проблем, не используя super () .Изменение SubClassAgain на следующее предотвратит бесконечную рекурсию:

@class_decorator
class SubClassAgain(BaseClass):
    def print_class(self):
        BaseClass.print_class(self)
3
ответ дан 6 December 2019 в 21:11
поделиться

Вы уверены, что хотите использовать декоратор класса, а не просто наследование? Например, вместо декоратора для замены вашего класса подклассом, вводящим некоторые методы, возможно, вы хотите использовать класс миксина и использовать множественное наследование для создания последнего класса?

Это можно сделать с помощью чего-то вроде

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() 
2
ответ дан 6 December 2019 в 21:11
поделиться

Как вы, возможно, уже знаете, проблема возникает из-за того, что имя 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()

Другой способ - использовать свойство __ base __ для получения декорированного класса.

@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

Это, вероятно, лучший вариант для вашего примера, хотя декоратор на основе миксинов также имеет свое применение.

2
ответ дан 6 December 2019 в 21:11
поделиться

Как насчет простого продвижения _DecoratedClass __ баз __ до ] __ баз __ из SubClassAgain ?

def class_decorator(cls):
    class _DecoratedClass(cls):
        def __init__(self):
            return super(_DecoratedClass, self).__init__()
    _DecoratedClass.__bases__=cls.__bases__
    return _DecoratedClass
0
ответ дан 6 December 2019 в 21:11
поделиться

В принципе, вы можете увидеть проблему после ввода вашего примера кода в интерактивной подсказке 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
ответ дан 6 December 2019 в 21:11
поделиться
Другие вопросы по тегам:

Похожие вопросы: