Python 2.7で抽象クラスメソッドのデコレータを作成するにはどうすればよいですか?
はい、これはこの質問に似ていますが、の代わりにabc.abstractmethod
とを組み合わせたい点が異なります。また、Python 3で追加されたようですが(私は思いますか?)、Google App Engineを使用しているため、現在Python2.7に制限されています。classmethod
staticmethod
abc.abstractclassmethod
前もって感謝します。
Python 2.7で抽象クラスメソッドのデコレータを作成するにはどうすればよいですか?
はい、これはこの質問に似ていますが、の代わりにabc.abstractmethod
とを組み合わせたい点が異なります。また、Python 3で追加されたようですが(私は思いますか?)、Google App Engineを使用しているため、現在Python2.7に制限されています。classmethod
staticmethod
abc.abstractclassmethod
前もって感謝します。
Python3.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()
classmethodsが通常行うように呼び出すか、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からインスタンス化することはできません。これは、classmethodを使用して実装すると機能します。
Python3にアップグレードできます。
Python 3.3以降、とを組み合わせる ことができます。@classmethod
@abstractmethod
import abc
class Foo(abc.ABC):
@classmethod
@abc.abstractmethod
def my_abstract_classmethod(...):
pass
これを私に指摘してくれた@gerritに感謝します。
最近、同じ問題が発生しました。つまり、抽象クラスメソッドが必要でしたが、他のプロジェクトの制約のためにPython3を使用できませんでした。私が思いついた解決策は次のとおりです。
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()
このソリューションのパフォーマンスコストは評価していませんが、これまでのところ、私の目的には役立っています。