2012-06-27 15 views
25

Python 2.7で抽象クラスのメソッドのデコレータを作成するにはどうすればよいですか?Python 2.7 abc.abstractmethodとclassmethodの組み合わせ

はい、これは、staticmethodの代わりにabc.abstractmethodclassmethodを組み合わせたい場合を除き、this questionと似ています。またabc.abstractclassmethodadded in Python 3 (I think?)だったようですが、Google App Engineを使用していますので、現在はPython 2.7に限定されています。

ありがとうございます。ここで

+0

abc.abstractclassmethodを使用できない場合は、他のデコレータと組み合わせる方法の問題はどのように発生しますか? – abarnert

+3

あなたは誤解していると思う:abc.abstractclassmethod = abc.abstractmethod + classmethod。 Python 2.7にはabstractmethodとclassmethodがありますが、abstractclassmethodではありません – jchu

+1

私は全く同じ問題に直面しています!良い質問! –

答えて

24

は、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は、任意の抽象メソッドを防止するための努力を行うものではありませんインスタンス化されていないクラスで呼び出されることはありません。したがって、通常はclassmethodsのようにcls()を呼び出すか、NotImplementedErrorを呼び出すことによって、失敗をトリガーする必要があります。いずれにしても、きれいでクリーンな失敗が得られます。

おそらく、抽象クラスのメソッドへの直接呼び出しをインターセプトする記述子を書き込むために誘惑されたが、それはむしろ以前のすべてのインスタンス化に必要なメソッドのチェックについてですABCMeta(の全体的な設計と対立だろうメソッドが呼び出されるときよりも)。

+2

Pythonのソースコードをチェックすることをお勧めします。残念ながら、 'classmethod'と' abc.abstractmethod'の後ろに似ていますが、これは 'abstractmethod'を強制しません。すなわち、私はまだメソッドを呼び出すことができます。 – jchu

+1

@jchu ABCMetaは、抽象メソッドが見つからないときにクラスのインスタンス化を防ぐことを約束しています。インスタンス化されていないクラスの抽象メソッドへの呼び出しを防ぐコードはありません。抽象クラスメソッドの呼び出しでクリーンな失敗をしたい場合は、* NotImplementedError *を発生させるか、 '' cls() ''でインスタンス化を試みてください。 –

+0

クラスメソッドからオブジェクトをインスタンス化するのは一般的なPythonパターンです。私の特定のユースケースでは、classmethod内からオブジェクトをインスタンス化するのは意味がありません。私は、代替オプションで言及したように、抽象基本クラスでNotImplementedErrorを発生させました。しかし、その場合、abstractclassmethodデコレータまたはclassmethodデコレータを使用した場合(意図の明確さに加えて)、動作に違いはありますか? – jchu

2

私は最近、同じ問題に遭遇しました。つまり、私は抽象クラスメソッドが必要でしたが、他のプロジェクトの制約のために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() 

私は、このソリューションのパフォーマンスコストを評価されていないが、それはこれまでのところ、私の目的のために働いています。

14

もう一つの可能​​な回避策:「some_classmethod」は実装されている、とあなたはクラスメソッドでそれを実装する場合、それが動作するまで

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 

、1はまだAからインスタンス化することはできません。

+0

これがトップの回答になるはずです –

関連する問題