Python 2.7 Combine abc.abstractmethod y classmethod

¿Cómo creo un decorador para un método de clase abstracto en Python 2.7?

Sí, esto es similar a esta pregunta , excepto que me gustaría combinar el abc.abstractmethod y el classmethod , en lugar del staticmethod . Además, parece que se agregó abc.abstractclassmethod en Python 3 (¿creo?) , Pero estoy usando Google App Engine, por lo que actualmente estoy limitado a Python 2.7

Gracias por adelantado.

Aquí hay un ejemplo de trabajo derivado del código fuente en el módulo abc de Python 3.3:

 from abc import ABCMeta class abstractclassmethod(classmethod): __isabstractmethod__ = True def __init__(self, callable): callable.__isabstractmethod__ = True super(abstractclassmethod, self).__init__(callable) class DemoABC: __metaclass__ = ABCMeta @abstractclassmethod def from_int(cls, n): return cls() class DemoConcrete(DemoABC): @classmethod def from_int(cls, n): return cls(2*n) def __init__(self, n): print 'Initializing with', n 

Esto es lo que parece cuando se ejecuta:

 >>> d = DemoConcrete(5) # Succeeds by calling a concrete __init__() Initializing with 5 >>> d = DemoConcrete.from_int(5) # Succeeds by calling a concrete from_int() Initializing with 10 >>> DemoABC() # Fails because from_int() is abstract Traceback (most recent call last): ... TypeError: Can't instantiate abstract class DemoABC with abstract methods from_int >>> DemoABC.from_int(5) # Fails because from_int() is not implemented Traceback (most recent call last): ... TypeError: Can't instantiate abstract class DemoABC with abstract methods from_int 

Tenga en cuenta que el ejemplo final falla porque cls() no crea una instancia. ABCMeta evita la creación prematura de clases que no han definido todos los métodos abstractos requeridos.

Otra forma de desencadenar un error cuando se llama al método de clase abstracta from_int () es hacer que genere una excepción:

 class DemoABC: __metaclass__ = ABCMeta @abstractclassmethod def from_int(cls, n): raise NotImplementedError 

El diseño ABCMeta no hace ningún esfuerzo para evitar que se llame a ningún método abstracto en una clase no demostrada, por lo que depende de usted desencadenar un error al invocar cls() como lo hacen los métodos de clase o al generar un error NotImplemented . De cualquier manera, obtienes un buen y limpio fracaso.

Probablemente sea tentador escribir un descriptor para interceptar una llamada directa a un método de clase abstracto, pero eso estaría en desacuerdo con el diseño general de ABCMeta (que consiste en verificar los métodos requeridos antes de la instanciación en lugar de cuando se llaman métodos) .

Otra posible solución:

 class A: __metaclass__ = abc.ABCMeta @abc.abstractmethod def some_classmethod(cls): """IMPORTANT: this is class method, override it with @classmethod!""" pass class B(A): @classmethod def some_classmethod(cls): print cls 

Ahora, uno todavía no puede crear una instancia desde A hasta que se implementa ‘some_classmethod’, y funciona si lo implementa con un classmethod.

Recientemente me encontré con el mismo problema. Es decir, necesitaba métodos de clase abstractos pero no pude usar Python 3 debido a otras restricciones del proyecto. La solución que se me ocurrió es la siguiente.

abcExtend.py:

 import abc class instancemethodwrapper(object): def __init__(self, callable): self.callable = callable self.__dontcall__ = False def __getattr__(self, key): return getattr(self.callable, key) def __call__(self, *args, **kwargs): if self.__dontcall__: raise TypeError('Attempted to call abstract method.') return self.callable(*args,**kwargs) class newclassmethod(classmethod): def __init__(self, func): super(newclassmethod, self).__init__(func) isabstractmethod = getattr(func,'__isabstractmethod__',False) if isabstractmethod: self.__isabstractmethod__ = isabstractmethod def __get__(self, instance, owner): result = instancemethodwrapper(super(newclassmethod, self).__get__(instance, owner)) isabstractmethod = getattr(self,'__isabstractmethod__',False) if isabstractmethod: result.__isabstractmethod__ = isabstractmethod abstractmethods = getattr(owner,'__abstractmethods__',None) if abstractmethods and result.__name__ in abstractmethods: result.__dontcall__ = True return result class abstractclassmethod(newclassmethod): def __init__(self, func): func = abc.abstractmethod(func) super(abstractclassmethod,self).__init__(func) 

Uso:

 from abcExtend import abstractclassmethod class A(object): __metaclass__ = abc.ABCMeta @abstractclassmethod def foo(cls): return 6 class B(A): pass class C(B): @classmethod def foo(cls): return super(C,cls).foo() + 1 try: a = A() except TypeError: print 'Instantiating A raises a TypeError.' try: A.foo() except TypeError: print 'Calling A.foo raises a TypeError.' try: b = B() except TypeError: print 'Instantiating B also raises a TypeError because foo was not overridden.' try: B.foo() except TypeError: print 'As does calling B.foo.' #But C can be instantiated because C overrides foo c = C() #And C.foo can be called print C.foo() 

Y aquí hay algunas pruebas de Pyunit que dan una demostración más exhaustiva.

testAbcExtend.py:

 import unittest import abc oldclassmethod = classmethod from abcExtend import newclassmethod as classmethod, abstractclassmethod class Test(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def testClassmethod(self): class A(object): __metaclass__ = abc.ABCMeta @classmethod @abc.abstractmethod def foo(cls): return 6 class B(A): @classmethod def bar(cls): return 5 class C(B): @classmethod def foo(cls): return super(C,cls).foo() + 1 self.assertRaises(TypeError,A.foo) self.assertRaises(TypeError,A) self.assertRaises(TypeError,B) self.assertRaises(TypeError,B.foo) self.assertEqual(B.bar(),5) self.assertEqual(C.bar(),5) self.assertEqual(C.foo(),7) def testAbstractclassmethod(self): class A(object): __metaclass__ = abc.ABCMeta @abstractclassmethod def foo(cls): return 6 class B(A): pass class C(B): @oldclassmethod def foo(cls): return super(C,cls).foo() + 1 self.assertRaises(TypeError,A.foo) self.assertRaises(TypeError,A) self.assertRaises(TypeError,B) self.assertRaises(TypeError,B.foo) self.assertEqual(C.foo(),7) c = C() self.assertEqual(c.foo(),7) if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() 

No he evaluado el costo de rendimiento de esta solución, pero hasta ahora ha funcionado para mis propósitos.