32

Python 2.7で抽象クラスメソッドのデコレータを作成するにはどうすればよいですか?

はい、これはこの質問に似ていますが、の代わりにabc.abstractmethodとを組み合わせたい点が異なります。また、Python 3で追加されたようですが(私は思いますか?)、Google App Engineを使用しているため、現在Python2.7に制限されています。classmethodstaticmethodabc.abstractclassmethod

前もって感謝します。

4

4 に答える 4

30

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の全体的な設計(メソッドが呼び出されるときではなく、インスタンス化の前に必要なメソッドをチェックすることに関するものです)とは相容れません。 。

于 2012-06-27T02:12:24.883 に答える
20

別の可能な回避策:

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を使用して実装すると機能します。

于 2014-06-04T18:50:04.910 に答える
9

Python3にアップグレードできます。

Python 3.3以降、とを組み合わせる ことができます。@classmethod@abstractmethod

import abc
class Foo(abc.ABC):
    @classmethod
    @abc.abstractmethod
    def my_abstract_classmethod(...):
        pass

これを私に指摘してくれた@gerritに感謝します。

于 2020-02-12T03:39:43.070 に答える
2

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

このソリューションのパフォーマンスコストは評価していませんが、これまでのところ、私の目的には役立っています。

于 2012-11-30T06:17:29.903 に答える