Это не сработает. Ответ yAzou может выглядеть так, как будто он работает, но это не так - он пропускает важную инициализацию и оставляет MessageBase в нерабочем состоянии; наиболее очевидным признаком является то, что даже если вы заполните _fields_
и реализуете абстрактное свойство в конкретном подклассе, , вы по-прежнему не сможете создавать экземпляры .
Попытка выполнить множественное наследование с типами, записанными в C, не поддерживается. Частично поведение может быть ошибкой Python, но я сомневаюсь, что она когда-нибудь будет хорошо работать. Вот что происходит.
_ctypes.PyCStructType
- это метакласс ctypes.Structure
. Этот метакласс написан в C, что означает, что его __new__
создается как оболочка вокруг своего C -уровня tp_new
слота.
Обертка tp_new
хочет чтобы убедиться, что вы никогда не пропустите инициализацию уровня C, выполнив что-то вроде object.__new__(tuple)
(или, что еще хуже, tuple.__new__(dict)
), поскольку это может привести к повреждению памяти. По этой причине он имеет следующую проверку безопасности :
static PyObject *
tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds)
{
...
/* Check that the use doesn't do something silly and unsafe like
object.__new__(dict). To do this, we check that the
most derived base that's not a heap type is this type. */
staticbase = subtype;
while (staticbase && (staticbase->tp_new == slot_tp_new))
staticbase = staticbase->tp_base;
/* If staticbase is NULL now, it is a really weird type.
In the spirit of backwards compatibility (?), just shut up. */
if (staticbase && staticbase->tp_new != type->tp_new) {
PyErr_Format(PyExc_TypeError,
"%s.__new__(%s) is not safe, use %s.__new__()",
type->tp_name,
subtype->tp_name,
staticbase->tp_name);
return NULL;
}
Это пытается убедиться, что когда вы звоните ClassA.__new__(ClassB, ...)
с ClassA
, записанным в C, ClassA
- это класс C, самый дальний по иерархии наследования ClassB
. Однако он следует за цепочкой одиночных базовых классов через tp_base
указателей, игнорируя множественное наследование !
Способом инициализации указателей tp_base
(см. best_base
в Objects/typeobject.c
), следующие за tp_base
указатели обычно приводят к наиболее производному C классу, но это зависит от каждого C класса, добавляющего поля в свой (единственный) базовый класс макет памяти экземпляра. _ctypes.PyCStructType
этого не делает (он заменяет класс dict на странный настраиваемый подкласс dict для хранения своих данных), поэтому цепочка tp_base
для FinalMeta переходит в FinalMeta-> ABCMeta-> type-> object.
Поскольку _ctypes.PyCStructType
не входит в цепочку tp_base
, tp_new_wrapper
думает, что type
является наиболее производным C предком FinalMeta, и думает, что вам следует вместо этого позвонить type.__new__
. Однако если вы вызовете type.__new__
, вы пропустите инициализацию, эта проверка должна была остановить вас от пропуска, оставив ваш класс в нерабочем состоянии.
Вы можете подумать, что можете исправить это, переставив основания так, чтобы type(Structure)
перед ABCMeta
, но это решает только половину проблемы. Вы сможете определить свой MessageBase
класс, но ABCMeta.__new__
не будет работать, и он не будет выполнять инициализацию, необходимую для пометки MessageBase
аннотации.
Кроме того, есть полностью несвязанная, давняя проблема, когда, даже если MessageBase
был помечен как абстрактное, вы все равно могли бы создавать его экземпляры. Проверка, предотвращающая создание экземпляров абстрактных классов, находится в object.__new__
, а C классы, отличные от object
(и потомки таких классов), не вызывают object.__new__
.