Я пытаюсь выяснить, как использовать декораторов на подклассах то использование 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 повреждение его.
Декоратор создает ситуацию наследования типа ромба. Вы можете избежать этих проблем, не используя super ()
.Изменение SubClassAgain
на следующее предотвратит бесконечную рекурсию:
@class_decorator
class SubClassAgain(BaseClass):
def print_class(self):
BaseClass.print_class(self)
Вы уверены, что хотите использовать декоратор класса, а не просто наследование? Например, вместо декоратора для замены вашего класса подклассом, вводящим некоторые методы, возможно, вы хотите использовать класс миксина и использовать множественное наследование для создания последнего класса?
Это можно сделать с помощью чего-то вроде
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()
Как вы, возможно, уже знаете, проблема возникает из-за того, что имя 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
Это, вероятно, лучший вариант для вашего примера, хотя декоратор на основе миксинов также имеет свое применение.
Как насчет простого продвижения _DecoratedClass
__ баз __
до ] __ баз __
из SubClassAgain
?
def class_decorator(cls):
class _DecoratedClass(cls):
def __init__(self):
return super(_DecoratedClass, self).__init__()
_DecoratedClass.__bases__=cls.__bases__
return _DecoratedClass
В принципе, вы можете увидеть проблему после ввода вашего примера кода в интерактивной подсказке 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()