Украшенные классы наследования и методы классов
Как правильно оформить класс, чтобы иметь возможность наследовать классы от украшенного? Существует ли правильный путь?
Если я сделаю что-то вроде:
def singleton(cls):
inst = {}
def get(*args, **kwargs):
cls_id = id(cls)
if cls_id not in inst:
inst[cls_id] = cls(*args, **kwargs)
return inst[cls_id]
return get
@singleton
class A(object):
@classmethod
def cls_meth(cls):
return cls
У меня нет шансов унаследовать класс выше одного, потому что, прежде чем я его назову, это функция. Та же проблема с методами класса, у функции нет методов класса.
class B(A): # No way! `A` is the function!
pass
A.cls_meth() # AttributeError: 'function' object has no attribute 'cls_meth'
Даже я делаю что-то вроде:
class SingletonDecorator(object):
inst = {}
def __init__(self, cls):
self.cls = cls
def __call__(self, *args, **kwargs):
cls_id = id(self.cls)
if cls_id not in self.inst:
self.inst[cls_id] = self.cls(*args, **kwargs)
return self.inst[cls_id]
@SingletonDecorator
class A(object):
pass
Когда я наследую класс от A
класс, он будет унаследован от SingletonDecorator
класс вместо A
,
class B(A): # No no no! I do not need a `SingletonDecorator` child class...
pass
Есть способ изменить экземпляр класса через __metaclass__
, но это совсем другая история...
4 ответа
Ваш декоратор должен вернуть исходный класс (или его подкласс), чтобы иметь возможность подклассировать его дальше. Ваш декоратор возвращает экземпляр самого себя, когда используется в качестве декоратора (потому что это поведение по умолчанию для вызова класса).
Например, что-то вроде этого:
def singleton(cls):
class C(cls):
_instance = None
def __new__(c, *args, **kwargs):
if type(c._instance) != c:
c._instance = cls.__new__(c, *args, **kwargs)
return c._instance
C.__name__ = cls.__name__
return C
Это возвращает подкласс исходного класса с переопределенным __new__()
метод, который делает магию синглтона. Этот класс является подклассом.
Однако практически никогда нет причин создавать синглтон. Часто это просто способ скрыть глобальное состояние, которое лучше устранить.
Немного подумать над этим результатом:
class SD(object):
""" The right way to decorate classes """
inst = {}
def __init__(self, cls):
self.__dict__.update(cls.__dict__)
self.__name__ = cls.__name__
self.cls = cls
def __call__(self, *args, **kwargs):
if self.cls not in self.inst:
self.inst[self.cls] = self.cls(*args, **kwargs)
return self.inst[self.cls]
Вдохновленный ответом и комментариями @kindall, я пришел к другому решению. Кстати, синглтон - всего лишь пример, он не имеет реального отношения к этому вопросу.
Чтобы иметь возможность наследовать от декорированного класса, вы должны убедиться, что декоратор возвращает декорированный класс сам, а не оболочку. Вы можете манипулировать созданием новых объектов из этого класса, перегружая __new__()
-оператором. Делая это, вы даже можете вызывать методы декоратора, если сам декоратор является классом. И, конечно, вы можете использовать наследование для декоратора.
Полный пример см.
Ваш декоратор должен вернуть класс, чтобы вы могли создать его подкласс.
>>> def decorator(cls):
... class Stuff(cls):
... @classmethod
... def print_result(kls):
... print 'results= %s' % kls.results
... return Stuff
...
>>> class Foo(object):
... results = 'grade A'
... @classmethod
... def print_result(cls):
... print cls.results
...
>>>
>>> @decorator
... class Bar(Foo):
... pass
...
>>> class Baz(Bar):
... def frobnicate(self):
... pass
...
>>>
>>> Baz.print_result
<bound method type.print_result of <class '__main__.Baz'>>
>>> Baz.print_result()
results= grade A
>>> Foo.print_result
<bound method type.print_result of <class '__main__.Foo'>>
>>> Foo.print_result()
grade A
>>>