如何在 Python 2.7 中为抽象类方法创建装饰器?
是的,这类似于这个问题,除了我想结合abc.abstractmethodand classmethod,而不是staticmethod. 此外,它看起来像是在 Python 3abc.abstractclassmethod中添加的(我认为?) ,但我使用的是 Google App Engine,所以我目前仅限于 Python 2.7
提前致谢。
如何在 Python 2.7 中为抽象类方法创建装饰器?
是的,这类似于这个问题,除了我想结合abc.abstractmethodand classmethod,而不是staticmethod. 此外,它看起来像是在 Python 3abc.abstractclassmethod中添加的(我认为?) ,但我使用的是 Google App Engine,所以我目前仅限于 Python 2.7
提前致谢。
这是一个从 Python 3.3 的abc模块中的源代码派生的工作示例:
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
这是运行时的样子:
>>> 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
请注意,最后一个示例失败,因为cls()不会实例化。 ABCMeta防止未定义所有必需抽象方法的类的过早实例化。
当调用 from_int()抽象类方法时触发失败的另一种方法是让它引发异常:
class DemoABC:
__metaclass__ = ABCMeta
@abstractclassmethod
def from_int(cls, n):
raise NotImplementedError
设计ABCMeta不努力防止在未实例化的类上调用任何抽象方法,因此您可以通过调用cls()类方法通常执行的操作或通过引发NotImplementedError来触发失败。无论哪种方式,你都会得到一个很好的、干净的失败。
编写一个描述符来拦截对抽象类方法的直接调用可能很诱人,但这与ABCMeta的整体设计不一致(这完全是在实例化之前检查所需的方法,而不是在调用方法时) .
另一种可能的解决方法:
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
现在,在实现“some_classmethod”之前,仍然无法从 A 实例化,如果您使用类方法实现它,它就可以工作。
您可以升级到Python 3。
从Python 3.3开始,可以组合 @classmethod和@abstractmethod:
import abc
class Foo(abc.ABC):
@classmethod
@abc.abstractmethod
def my_abstract_classmethod(...):
pass
感谢@gerrit 向我指出这一点。
我最近遇到了同样的问题。也就是说,我需要抽象类方法,但由于其他项目限制而无法使用 Python 3。我想出的解决方案如下。
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)
用法:
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()
这里有一些 pyunit 测试,它们提供了更详尽的演示。
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()
我还没有评估这个解决方案的性能成本,但到目前为止它已经达到了我的目的。