Могут ли абстрактные базовые классы Python наследоваться от расширений C?

Кажется, что когда у меня есть абстрактный базовый класс, который наследуется от 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

Как правильно реализовать эту функцию?


person Dustin Wyatt    schedule 06.12.2013    source источник
comment
В целом должно быть нормально. Однако gevent.Greenlet не может быть классом нового стиля, который может помешать работе механизма ABC.   -  person chepner    schedule 07.12.2013
comment
Упс, ты прав. gevent.Greenlet наследуется от модуля расширения C. Я отредактирую вопрос, чтобы спросить, какой хороший способ реализовать этот вариант использования.   -  person Dustin Wyatt    schedule 07.12.2013
comment
В общем случае наследование от типа, реализованного в C, не должно быть проблемой. Должно быть что-то конкретное для самого класса greenlet.greenlet, что мешает.   -  person Mark Dickinson    schedule 07.12.2013


Ответы (1)


Причина вашей проблемы в том, что это метод object.__new__ который выполняет проверку создания экземпляра абстрактного класса, и в этом случае object.__new__ не вызывается: gevent.Greenlet наследуется от greenlet.greenlet, а greenlet.greenlet является типом расширения C, реализация которого __new__ не вызывает object.__new__ в любой момент (см. href="https://github.com/python-greenlet/greenlet/blob/master/greenlet.c#L840" rel="noreferrer">green_new в исходном коде greenlet C).

Вы можете увидеть тот же эффект, создав подклассы некоторых других встроенных типов, которые реализуют свой собственный метод __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__ (и отбрасывает результат последнего), но я бы посчитал это уродливым обходным путем, а не 'правильно'. (EDIT: И, кроме того, это не работает. Я получаю TypeError: object.__new__(ActorBase) is not safe, use greenlet.greenlet.__new__() из вызова object.__new__.) Я открыл проблема в системе отслеживания гринлетов.


РЕДАКТИРОВАТЬ: эта проблема показалась несколько знакомой, и я только что немного покопался в источнике Enthought Traits, который определяет CHasTraits, реализованный на C, который действительно прекрасно работает с 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 сделать что-то подобное.

person Mark Dickinson    schedule 07.12.2013
comment
Отличный ответ. Спасибо. - person Dustin Wyatt; 07.12.2013
comment
Для всех, кто придет позже, проблема @Mark Dickinson, поднятая в трекере гринлетов, привела к тому, что гринлет внедрил исправление. Проблема, о которой я писал, теперь исправлена ​​для гринлетов. - person Dustin Wyatt; 03.08.2020