【问题标题】:Python 2.7 Combine abc.abstractmethod and classmethodPython 2.7 结合 abc.abstractmethod 和 classmethod
【发布时间】:2012-06-28 09:26:26
【问题描述】:

如何在 Python 2.7 中为抽象类方法创建装饰器?

是的,这类似于this question,除了我想组合abc.abstractmethodclassmethod,而不是staticmethod。另外,看起来abc.abstractclassmethodadded in Python 3 (I think?),但我使用的是 Google App Engine,所以我目前仅限于 Python 2.7

提前致谢。

【问题讨论】:

  • 如果你不能使用abc.abstractclassmethod,那么如何与另一个装饰器结合的问题呢?
  • 认为你误读了:abc.abstractclassmethod = abc.abstractmethod + classmethod。 Python 2.7有abstractmethod和classmethod,但没有abstractclassmethod
  • 我面临着完全相同的问题!好问题!

标签: python google-app-engine python-2.7


【解决方案1】:

您可以升级到 Python 3

Python 3.3开始,分别是possible to combine@classmethod@abstractmethod

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

感谢@gerrit 向我指出这一点。

【讨论】:

    【解决方案2】:

    另一种可能的解决方法:

    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 实现它,它就可以工作。

    【讨论】:

      【解决方案3】:

      这是一个从 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 的整体设计(即检查所需方法的全部内容)不一致在实例化之前而不是在调用方法时)。

      【讨论】:

      • 检查Python源代码的好主意,但不幸的是,类似于我写classmethod,然后是abc.abstractmethod,这并不强制abstractmethod。即我仍然可以调用该方法。
      • @jchu ABCMeta 是关于在缺少抽象方法时防止类实例化。它没有代码来阻止对未实例化类的抽象方法的调用。如果您想在调用抽象类方法时彻底失败,则让它引发 NotImplementedError 或让它尝试使用 cls() 实例化。
      • 那么从类方法实例化对象是一种常见的 Python 模式?对于我的特定用例,从类方法中实例化一个对象是没有意义的,这可能解释了我的断开连接。正如您在替代选项中提到的那样,我最终在抽象基类中提出了 NotImplementedError 。但是在这种情况下,如果您使用 abstractclassmethod 装饰器或 classmethod 装饰器(除了意图清晰),行为是否有任何区别?
      • 是的,类方法的主要用例是提供替代构造函数,例如 datetime.now()dict.fromkeys() .是的,abstractclassmethod 和普通的classmethod 是有区别的。除了意图更清晰之外,缺少的 abstractclassmethod 将阻止类的实例化,即使是普通的构造函数,参见上面的第三个示例 DemoABC()ABCMeta 元类在实例化之前检查整个抽象方法todo-list
      • 明白了。似乎 abstractclassmethod 的意图与我的想法不同。感谢您的澄清。
      【解决方案4】:

      我最近遇到了同样的问题。也就是说,我需要抽象类方法,但由于其他项目限制而无法使用 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()
      

      我尚未评估此解决方案的性能成本,但到目前为止,它已达到我的目的。

      【讨论】:

        猜你喜欢
        • 2011-05-27
        • 2016-05-03
        • 2013-01-18
        • 1970-01-01
        • 2014-12-01
        • 1970-01-01
        • 2013-02-23
        • 2012-06-02
        相关资源
        最近更新 更多