Декораторы классов, Наследование, супер () и максимальная рекурсия - PullRequest
7 голосов
/ 30 марта 2010

Я пытаюсь выяснить, как использовать декораторы на подклассах, которые используют 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() при использовании декоратора? В идеале я хотел бы время от времени повторно использовать класс и просто украшать его, не ломая его.

Ответы [ 5 ]

5 голосов
/ 30 марта 2010

В принципе, вы можете увидеть проблему после ввода кода в интерактивном окне 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()
3 голосов
/ 30 марта 2010

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

@class_decorator
class SubClassAgain(BaseClass):
    def print_class(self):
        BaseClass.print_class(self)
2 голосов
/ 30 марта 2010

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

Конечно, с несколькими декораторами любой из них становится громоздким. Последний также не работает с подклассами оформленного класса. Вы можете комбинировать декораторы и миксины, написав декоратор, который добавляет миксины в класс. Это не поможет вам переопределить методы.

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 голосов
/ 30 марта 2010

Вы уверены, что хотите использовать декоратор классов, а не просто наследование? Например, вместо декоратора для замены вашего класса подклассом, представляющим некоторые методы, возможно, вам нужен класс 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() 
0 голосов
/ 30 марта 2010

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

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