Наследование от декорированных классов
Я пытаюсь украсить класс другим классом. Я также хочу наследовать от украшенного класса, но я получаю некоторые ошибки. Вот мой код:
class Decorator:
def __init__(self, decorated):
pass
@Decorator
class Foo:
pass
class Goo(Foo):
pass
Ошибка, которую я получаю, когда пытаюсь создать подкласс Foo
это:
Traceback (последний вызов был последним):
Файл "test.py", строка 9, в
класс Goo(Foo):
Ошибка типа: __init__() принимает ровно 2 позиционных аргумента (4 дано)
Добавив еще одну функцию инициализации Decorator
...
def __init__(self, *args):
for arg in args:
print(arg)
... я получаю следующий вывод:
Goo
(<__ main __. Объект Decorator в 0x010073B0>,)
{'__module__': '__main__'}
Каковы эти параметры и как я должен использовать их внутри Decorator
?
3 ответа
Я постараюсь ответить на вопрос "каковы эти параметры". Этот код:
@Decorator
class Foo:
pass
эквивалентно:
class Foo:
pass
Foo = Decorator(Foo)
Это означает, что Foo
заканчивается тем, что является экземпляром Decorator
класс вместо того, чтобы быть классом.
Когда вы пытаетесь использовать этот экземпляр в качестве основы класса (Goo
), Python должен будет определить метакласс, который будет использоваться для создания нового класса. В этом случае он будет использовать Foo.__class__
что равно Decorator
, Тогда он будет вызывать метакласс с (name, bases, dict)
аргументы и ожидать, что он вернет новый класс.
Вот как вы в конечном итоге с этими аргументами в Decorator.__init__
,
Подробнее об этом можно узнать здесь: http://www.python.org/download/releases/2.2.3/descrintro/(в частности, часть "Когда оператор класса выполняется...")
Вы пытаетесь добавить MixIn к классу после того, как класс был определен? Если это так, вы можете ввести MixIn следующим образом:
def inject_class(mixin):
def _inject_class(cls):
return type(cls.__name__,(mixin,)+cls.__bases__,dict(cls.__dict__))
return _inject_class
class MixIn(object):
def mix(self):
print('mix')
@inject_class(MixIn)
class Foo(object):
def foo(self):
print('foo')
class Goo(Foo):
def goo(self):
print('goo')
goo=Goo()
goo.mix()
goo.foo()
goo.goo()
печать
mix
foo
goo
Если вы не хотите общности inject_class
, вы можете сделать специализированный декоратор класса, который смешивается в Decorator
только:
def decorate(cls):
class Decorator(object):
def deco(self):
print('deco')
return type(cls.__name__,(Decorator,)+cls.__bases__,dict(cls.__dict__))
@decorate
class Foo(object):
def foo(self):
print('foo')
результат тот же.
У меня была такая же проблема, и у меня работает следующее решение:
from functools import update_wrapper
class decoratorBase():
def __new__(cls, logic):
self = object.__new__(cls)
self.__init__(logic)
def new (cls):
#cls is the decorated class type, not the decorator class type itself
self._createInstance(cls)
self._postInstanceCreation()
return self
self._logic.__new__ = new
#return the wrapped class and not a wrapper
return self._logic
def __init__(self, logic):
#logic is the decorated class
self._logic = logic
def _createInstance(self, cls):
self._logicInstance = object.__new__(cls)
self._logicInstance.__init__()
def _postInstanceCreation(self):
pass
class factory(decoratorBase):
def __init__(self, *largs, **kwargs):
super().__init__(*largs, **kwargs)
self.__instance = None
def _createInstance(self, cls):
self._logicInstance = None
self._cls = cls
def _postInstanceCreation(self):
update_wrapper(self, self._cls)
def __call__(self, userData, *largs, **kwargs):
logicInstance = object.__new__(self._cls)
logicInstance.__init__(*largs, **kwargs)
logicInstance._update(userData)
return logicInstance
class singelton(decoratorBase):
def _postInstanceCreation(self):
update_wrapper(self, self._logicInstance)
def __call__(self, userData):
self._logicInstance._update(userData)
return self._logicInstance
class base():
def __init__(self):
self.var = 0
print ("Create new object")
def __call__(self):
self.var += self._updateValue()
def _update(self, userData):
print ("Update object static value with {0}".format(userData))
self.var = userData
@factory
class factoryTestBase(base):
def __call__(self):
super().__call__()
print("I'm a factory, here is the proof: {0}".format(self.var))
def _updateValue(self):
return 1
class factoryTestDerived(factoryTestBase):
def _updateValue(self):
return 5
@singelton
class singeltonTestBase(base):
def __call__(self):
super().__call__()
print("I'm a singelton, here is the proof: {0}".format(self.var))
def _updateValue(self):
return 1
class singeltonTestDerived(singeltonTestBase):
def _updateValue(self):
return 5
Волшебство в этом подходе - перегрузка __new__()
метод, как и для самого декоратора, так и для "обертки", возвращаемой декоратором. Я устанавливаю слово "обертка" в кавычки, потому что на самом деле обертки нет. Вместо этого декорированный класс чередуется декоратором и возвращается. Используя эту схему, вы можете наследовать от декорированного класса. Самое главное, это изменение __new__()
метод декорированного класса, который производится по следующим строкам:
def new (cls):
self._createInstance(cls)
self._postInstanceCreation()
return self
self._logic.__new__ = new
Используя это, у вас есть доступ к таким методам декоратора, как self._createInstance()
при создании объекта из декорированного класса. У вас даже есть возможность наследовать от ваших декораторов (как показано в примере).
Теперь давайте запустим простой пример:
>>> factoryObjCreater = factoryTestBase()
>>> factoryObj1 = factoryObjCreater(userData = 1)
Create new object
Update object static value with 1
>>> factoryObj2 = factoryObjCreater(userData = 1)
Create new object
Update object static value with 1
>>> factoryObj1()
I'm a factory, here is the proof: 2
>>> factoryObj2()
I'm a factory, here is the proof: 2
>>> factoryObjDerivedCreater = factoryTestDerived()
>>> factoryObjDerived1 = factoryObjDerivedCreater(userData = 2)
Create new object
Update object static value with 2
>>> factoryObjDerived2 = factoryObjDerivedCreater(userData = 2)
Create new object
Update object static value with 2
>>> factoryObjDerived1()
I'm a factory, here is the proof: 7
>>> factoryObjDerived2()
I'm a factory, here is the proof: 7
>>> singeltonObjCreater = singeltonTestBase()
Create new object
>>> singeltonObj1 = singeltonObjCreater(userData = 1)
Update object static value with 1
>>> singeltonObj2 = singeltonObjCreater(userData = 1)
Update object static value with 1
>>> singeltonObj1()
I'm a singelton, here is the proof: 2
>>> singeltonObj2()
I'm a singelton, here is the proof: 3
>>> singeltonObjDerivedCreater = singeltonTestDerived()
Create new object
>>> singeltonObjDerived1 = singeltonObjDerivedCreater(userData = 2)
Update object static value with 2
>>> singeltonObjDerived2 = singeltonObjDerivedCreater(userData = 2)
Update object static value with 2
>>> singeltonObjDerived1()
I'm a singelton, here is the proof: 7
>>> singeltonObjDerived2()
I'm a singelton, here is the proof: 12
>>>