Python 2.7 Combine abc.abstractmethod and classmethod

前端 未结 4 1440
太阳男子
太阳男子 2020-12-15 04:19

How do I create a decorator for an abstract class method in Python 2.7?

Yes, this is similar to this question, except I would like to combine abc.abstractmet

4条回答
  •  不思量自难忘°
    2020-12-15 04:57

    I recently encountered the same problem. That is, I needed abstract classmethods but was unable to use Python 3 because of other project constraints. The solution I came up with is the following.

    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)
    

    Usage:

    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()
    

    And here are some pyunit tests which give a more exhaustive demonstration.

    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()
    

    I haven't evaluated the performance cost of this solution, but it has worked for my purposes so far.

提交回复
热议问题