Recientemente he encontrado el mismo problema. Es decir, necesitaba métodos de clase abstractos pero no pude usar Python 3 debido a otras limitaciones 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í están algunas pruebas 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 ha trabajado para mis propósitos hasta el momento.
Si no puede usar abc.abstractclassmethod, ¿cómo surge la cuestión de cómo combinarlo con otro decorador? – abarnert
Creo que leyó mal: abc.abstractclassmethod = abc.abstractmethod + classmethod. Python 2.7 tiene abstract method y classmethod, pero no abstractclassmethod – jchu
¡Estoy enfrentando un problema exactamente similar! ¡Buena pregunta! –