似乎当我有一个继承自gevent.Greenlet的抽象基类(继承自C扩展模块greenlet:https://github.com/python-greenlet/greenlet)时,实现该类的类不会引发任何有关未实现方法的abc错误。

class ActorBase(gevent.Greenlet):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def foo(self):
        print "foo"

class ActorBaseTest(ActorBase):
    def bar(self):
        print "bar"

abt = ActorBaseTest()  # no errors!

如果我从object继承,它会按预期失败:
class ActorBase(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def foo(self):
        print "foo"

class ActorBaseTest(ActorBase):
    def bar(self):
        print "bar"

>>> abt = ActorBaseTest()
Traceback (most recent call last):
  File "/home/dw/.virtualenvs/prj/local/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2827, in run_code
exec code_obj in self.user_global_ns, self.user_ns
  File "<ipython-input-6-d67a142e7297>", line 1, in <module>
    abt = ActorBaseTest()
TypeError: Can't instantiate abstract class ActorBaseTest with abstract methods foo

什么是实现此功能的正确方法?

最佳答案

问题的原因在于,是由 object.__new__ 方法执行抽象类实例化的检查,在这种情况下,没有调用object.__new__:gevent.Greenlet继承自greenlet.greenlet,而greenlet.greenlet是C扩展类型,其__new__实现未实现” t随时调用object.__new__(请参阅green_new C源代码中的 greenlet 函数)。

通过将实现其自己的__new__方法的其他一些内置类型子类化,您可以看到相同的效果,而不必回头引用object.__new__(例如float类型)。但是,该问题并不只限于C扩展类型:您还可以使用纯Python类型来复制它。考虑下面的代码:

import abc

class A(object):
    def __new__(cls):
        # self = object.__new__(cls)
        return 42

class B(A):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def foo(self):
        pass

b = B()  # No exception.
B类已正确注册为抽象类(内部,在Py_TPFLAGS_IS_ABSTRACT字段中设置了其tp_flags位),但从未调用object.__new__,因此在实例化B时没有错误。但是,如果取消注释self = object.__new__(cls)中的A方法调用的注释,则会在实例化时看到预期的错误。

至于实现此目标的“正确方法”,不幸的是,我认为正确的方法是修复greenlet类型,以便其__new__方法调用object.__new__。我猜您可以在__new__中添加一个ActorBase方法,以显式调用基类__new__object.__new__(并丢弃后者的结果),但是我认为这是一个丑陋的解决方法,而不是“正确的方法”。 (编辑:最重要的是,它不起作用。我从TypeError: object.__new__(ActorBase) is not safe, use greenlet.greenlet.__new__()调用中获得了object.__new__。)我已经在greenlet跟踪器上打开了issue

编辑:这个问题似乎有点熟悉,我只是对Enthought Traits源进行了一些挖掘,它定义了用C实现的CHasTraits类,该类与ABC配合得很好。它的__new__方法是这样开始的(注释来自原始来源,而不是我的):
PyObject *
has_traits_new ( PyTypeObject * type, PyObject * args, PyObject * kwds ) {

    // Call PyBaseObject_Type.tp_new to do the actual construction.
    // This allows things like ABCMeta machinery to work correctly
    // which is implemented at the C level.
    has_traits_object * obj = (has_traits_object *) PyBaseObject_Type.tp_new(type, empty_tuple, empty_dict);

因此,也许长期的解决方案是说服greenlet员工做类似的事情。

关于python - python抽象基类可以从C扩展继承吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20432335/

10-10 11:21