mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +00:00
Make all classes with the same instance size derive from a common base
In order to fully satisfy Python's inheritance type layout requirements, all types should have a common 'solid' base. A solid base is one which has the same instance size as the derived type (not counting the space required for the optional `dict_ptr` and `weakrefs_ptr`). Thus, `object` does not qualify as a solid base for pybind11 types and this can lead to issues with multiple inheritance. To get around this, new base types are created: one per unique instance size. There is going to be very few of these bases. They ensure Python's MRO checks will pass when multiple bases are involved.
This commit is contained in:
parent
c91f8bd627
commit
08cbe8dfed
@ -26,6 +26,7 @@ struct type_info {
|
||||
PyTypeObject *type;
|
||||
size_t type_size;
|
||||
void (*init_holder)(PyObject *, const void *);
|
||||
void (*dealloc)(PyObject *);
|
||||
std::vector<PyObject *(*)(PyObject *, PyTypeObject *)> implicit_conversions;
|
||||
std::vector<std::pair<const std::type_info *, void *(*)(void *)>> implicit_casts;
|
||||
std::vector<bool (*)(PyObject *, void *&)> *direct_conversions;
|
||||
|
@ -85,6 +85,34 @@ inline PyTypeObject *make_static_property_type() {
|
||||
|
||||
#endif // PYPY
|
||||
|
||||
/** Inheriting from multiple C++ types in Python is not supported -- set an error instead.
|
||||
A Python definition (`class C(A, B): pass`) will call `tp_new` so we check for multiple
|
||||
C++ bases here. On the other hand, C++ type definitions (`py::class_<C, A, B>(m, "C")`)
|
||||
don't not use `tp_new` and will not trigger this error. */
|
||||
extern "C" inline PyObject *pybind11_meta_new(PyTypeObject *metaclass, PyObject *args,
|
||||
PyObject *kwargs) {
|
||||
PyObject *bases = PyTuple_GetItem(args, 1); // arguments: (name, bases, dict)
|
||||
if (!bases)
|
||||
return nullptr;
|
||||
|
||||
auto &internals = get_internals();
|
||||
auto num_cpp_bases = 0;
|
||||
for (auto base : reinterpret_borrow<tuple>(bases)) {
|
||||
auto base_type = (PyTypeObject *) base.ptr();
|
||||
auto instance_size = static_cast<size_t>(base_type->tp_basicsize);
|
||||
if (PyObject_IsSubclass(base.ptr(), internals.get_base(instance_size)))
|
||||
++num_cpp_bases;
|
||||
}
|
||||
|
||||
if (num_cpp_bases > 1) {
|
||||
PyErr_SetString(PyExc_TypeError, "Can't inherit from multiple C++ classes in Python."
|
||||
"Use py::class_ to define the class in C++ instead.");
|
||||
return nullptr;
|
||||
} else {
|
||||
return PyType_Type.tp_new(metaclass, args, kwargs);
|
||||
}
|
||||
}
|
||||
|
||||
/** Types with static properties need to handle `Type.static_prop = x` in a specific way.
|
||||
By default, Python replaces the `static_property` itself, but for wrapped C++ types
|
||||
we need to call `static_property.__set__()` in order to propagate the new value to
|
||||
@ -135,6 +163,8 @@ inline PyTypeObject* make_default_metaclass() {
|
||||
type->tp_name = name;
|
||||
type->tp_base = &PyType_Type;
|
||||
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
|
||||
|
||||
type->tp_new = pybind11_meta_new;
|
||||
type->tp_setattro = pybind11_meta_setattro;
|
||||
|
||||
if (PyType_Ready(type) < 0)
|
||||
@ -143,5 +173,328 @@ inline PyTypeObject* make_default_metaclass() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/// Instance creation function for all pybind11 types. It only allocates space for the
|
||||
/// C++ object, but doesn't call the constructor -- an `__init__` function must do that.
|
||||
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) {
|
||||
PyObject *self = type->tp_alloc(type, 0);
|
||||
auto instance = (instance_essentials<void> *) self;
|
||||
auto tinfo = get_type_info(type);
|
||||
instance->value = ::operator new(tinfo->type_size);
|
||||
instance->owned = true;
|
||||
instance->holder_constructed = false;
|
||||
get_internals().registered_instances.emplace(instance->value, self);
|
||||
return self;
|
||||
}
|
||||
|
||||
/// An `__init__` function constructs the C++ object. Users should provide at least one
|
||||
/// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the
|
||||
/// following default function will be used which simply throws an exception.
|
||||
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
|
||||
PyTypeObject *type = Py_TYPE(self);
|
||||
std::string msg;
|
||||
#if defined(PYPY_VERSION)
|
||||
msg += handle((PyObject *) type).attr("__module__").cast<std::string>() + ".";
|
||||
#endif
|
||||
msg += type->tp_name;
|
||||
msg += ": No constructor defined!";
|
||||
PyErr_SetString(PyExc_TypeError, msg.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
|
||||
/// to destroy the C++ object itself, while the rest is Python bookkeeping.
|
||||
extern "C" inline void pybind11_object_dealloc(PyObject *self) {
|
||||
auto instance = (instance_essentials<void> *) self;
|
||||
if (instance->value) {
|
||||
auto type = Py_TYPE(self);
|
||||
get_type_info(type)->dealloc(self);
|
||||
|
||||
auto ®istered_instances = get_internals().registered_instances;
|
||||
auto range = registered_instances.equal_range(instance->value);
|
||||
bool found = false;
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
if (type == Py_TYPE(it->second)) {
|
||||
registered_instances.erase(it);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
|
||||
|
||||
if (instance->weakrefs)
|
||||
PyObject_ClearWeakRefs(self);
|
||||
|
||||
PyObject **dict_ptr = _PyObject_GetDictPtr(self);
|
||||
if (dict_ptr)
|
||||
Py_CLEAR(*dict_ptr);
|
||||
}
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
/** Create a type which can be used as a common base for all classes with the same
|
||||
instance size, i.e. all classes with the same `sizeof(holder_type)`. This is
|
||||
needed in order to satisfy Python's requirements for multiple inheritance.
|
||||
Return value: New reference. */
|
||||
inline PyObject *make_object_base_type(size_t instance_size) {
|
||||
auto name = "pybind11_object_" + std::to_string(instance_size);
|
||||
auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name.c_str()));
|
||||
|
||||
/* Danger zone: from now (and until PyType_Ready), make sure to
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
|
||||
if (!heap_type)
|
||||
pybind11_fail("make_object_base_type(): error allocating type!");
|
||||
|
||||
heap_type->ht_name = name_obj.inc_ref().ptr();
|
||||
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
|
||||
heap_type->ht_qualname = name_obj.inc_ref().ptr();
|
||||
#endif
|
||||
|
||||
auto type = &heap_type->ht_type;
|
||||
type->tp_name = strdup(name.c_str());
|
||||
type->tp_base = &PyBaseObject_Type;
|
||||
type->tp_basicsize = static_cast<ssize_t>(instance_size);
|
||||
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
|
||||
|
||||
type->tp_new = pybind11_object_new;
|
||||
type->tp_init = pybind11_object_init;
|
||||
type->tp_dealloc = pybind11_object_dealloc;
|
||||
|
||||
/* Support weak references (needed for the keep_alive feature) */
|
||||
type->tp_weaklistoffset = offsetof(instance_essentials<void>, weakrefs);
|
||||
|
||||
if (PyType_Ready(type) < 0)
|
||||
pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string());
|
||||
|
||||
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
|
||||
return (PyObject *) heap_type;
|
||||
}
|
||||
|
||||
/** Return the appropriate base type for the given instance size. The results are cached
|
||||
in `internals.bases` so that only a single base is ever created for any size value.
|
||||
Return value: Borrowed reference. */
|
||||
inline PyObject *internals::get_base(size_t instance_size) {
|
||||
auto it = bases.find(instance_size);
|
||||
if (it != bases.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
auto base = make_object_base_type(instance_size);
|
||||
bases[instance_size] = base;
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
/// dynamic_attr: Support for `d = instance.__dict__`.
|
||||
extern "C" inline PyObject *pybind11_get_dict(PyObject *self, void *) {
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(self);
|
||||
if (!dict)
|
||||
dict = PyDict_New();
|
||||
Py_XINCREF(dict);
|
||||
return dict;
|
||||
}
|
||||
|
||||
/// dynamic_attr: Support for `instance.__dict__ = dict()`.
|
||||
extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) {
|
||||
if (!PyDict_Check(new_dict)) {
|
||||
PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'",
|
||||
Py_TYPE(new_dict)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(self);
|
||||
Py_INCREF(new_dict);
|
||||
Py_CLEAR(dict);
|
||||
dict = new_dict;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
|
||||
extern "C" inline int pybind11_traverse(PyObject *self, visitproc visit, void *arg) {
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(self);
|
||||
Py_VISIT(dict);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// dynamic_attr: Allow the GC to clear the dictionary.
|
||||
extern "C" inline int pybind11_clear(PyObject *self) {
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(self);
|
||||
Py_CLEAR(dict);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Give instances of this type a `__dict__` and opt into garbage collection.
|
||||
inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
|
||||
auto type = &heap_type->ht_type;
|
||||
#if defined(PYPY_VERSION)
|
||||
pybind11_fail(std::string(type->tp_name) + ": dynamic attributes are "
|
||||
"currently not supported in "
|
||||
"conjunction with PyPy!");
|
||||
#endif
|
||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
||||
type->tp_dictoffset = type->tp_basicsize; // place dict at the end
|
||||
type->tp_basicsize += sizeof(PyObject *); // and allocate enough space for it
|
||||
type->tp_traverse = pybind11_traverse;
|
||||
type->tp_clear = pybind11_clear;
|
||||
|
||||
static PyGetSetDef getset[] = {
|
||||
{const_cast<char*>("__dict__"), pybind11_get_dict, pybind11_set_dict, nullptr, nullptr},
|
||||
{nullptr, nullptr, nullptr, nullptr, nullptr}
|
||||
};
|
||||
type->tp_getset = getset;
|
||||
}
|
||||
|
||||
/// buffer_protocol: Fill in the view as specified by flags.
|
||||
extern "C" inline int pybind11_getbuffer(PyObject *obj, Py_buffer *view, int flags) {
|
||||
auto tinfo = get_type_info(Py_TYPE(obj));
|
||||
if (view == nullptr || obj == nullptr || !tinfo || !tinfo->get_buffer) {
|
||||
if (view)
|
||||
view->obj = nullptr;
|
||||
PyErr_SetString(PyExc_BufferError, "generic_type::getbuffer(): Internal error");
|
||||
return -1;
|
||||
}
|
||||
memset(view, 0, sizeof(Py_buffer));
|
||||
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
|
||||
view->obj = obj;
|
||||
view->ndim = 1;
|
||||
view->internal = info;
|
||||
view->buf = info->ptr;
|
||||
view->itemsize = (ssize_t) info->itemsize;
|
||||
view->len = view->itemsize;
|
||||
for (auto s : info->shape)
|
||||
view->len *= s;
|
||||
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
|
||||
view->format = const_cast<char *>(info->format.c_str());
|
||||
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
|
||||
view->ndim = (int) info->ndim;
|
||||
view->strides = (ssize_t *) &info->strides[0];
|
||||
view->shape = (ssize_t *) &info->shape[0];
|
||||
}
|
||||
Py_INCREF(view->obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// buffer_protocol: Release the resources of the buffer.
|
||||
extern "C" inline void pybind11_releasebuffer(PyObject *, Py_buffer *view) {
|
||||
delete (buffer_info *) view->internal;
|
||||
}
|
||||
|
||||
/// Give this type a buffer interface.
|
||||
inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) {
|
||||
heap_type->ht_type.tp_as_buffer = &heap_type->as_buffer;
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
heap_type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
|
||||
#endif
|
||||
|
||||
heap_type->as_buffer.bf_getbuffer = pybind11_getbuffer;
|
||||
heap_type->as_buffer.bf_releasebuffer = pybind11_releasebuffer;
|
||||
}
|
||||
|
||||
/** Create a brand new Python type according to the `type_record` specification.
|
||||
Return value: New reference. */
|
||||
inline PyObject* make_new_python_type(const type_record &rec) {
|
||||
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name));
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
|
||||
auto ht_qualname = name;
|
||||
if (rec.scope && hasattr(rec.scope, "__qualname__")) {
|
||||
ht_qualname = reinterpret_steal<object>(
|
||||
PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr()));
|
||||
}
|
||||
#endif
|
||||
|
||||
object module;
|
||||
if (rec.scope) {
|
||||
if (hasattr(rec.scope, "__module__"))
|
||||
module = rec.scope.attr("__module__");
|
||||
else if (hasattr(rec.scope, "__name__"))
|
||||
module = rec.scope.attr("__name__");
|
||||
}
|
||||
|
||||
#if !defined(PYPY_VERSION)
|
||||
const auto full_name = module ? str(module).cast<std::string>() + "." + rec.name
|
||||
: std::string(rec.name);
|
||||
#else
|
||||
const auto full_name = std::string(rec.name);
|
||||
#endif
|
||||
|
||||
char *tp_doc = nullptr;
|
||||
if (rec.doc && options::show_user_defined_docstrings()) {
|
||||
/* Allocate memory for docstring (using PyObject_MALLOC, since
|
||||
Python will free this later on) */
|
||||
size_t size = strlen(rec.doc) + 1;
|
||||
tp_doc = (char *) PyObject_MALLOC(size);
|
||||
memcpy((void *) tp_doc, rec.doc, size);
|
||||
}
|
||||
|
||||
auto &internals = get_internals();
|
||||
auto bases = tuple(rec.bases);
|
||||
auto base = (bases.size() == 0) ? internals.get_base(rec.instance_size)
|
||||
: bases[0].ptr();
|
||||
|
||||
/* Danger zone: from now (and until PyType_Ready), make sure to
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
|
||||
if (!heap_type)
|
||||
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
|
||||
|
||||
heap_type->ht_name = name.release().ptr();
|
||||
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
|
||||
heap_type->ht_qualname = ht_qualname.release().ptr();
|
||||
#endif
|
||||
|
||||
auto type = &heap_type->ht_type;
|
||||
type->tp_name = strdup(full_name.c_str());
|
||||
type->tp_doc = tp_doc;
|
||||
type->tp_base = (PyTypeObject *) handle(base).inc_ref().ptr();
|
||||
type->tp_basicsize = static_cast<ssize_t>(rec.instance_size);
|
||||
if (bases.size() > 0)
|
||||
type->tp_bases = bases.release().ptr();
|
||||
|
||||
/* Don't inherit base __init__ */
|
||||
type->tp_init = pybind11_object_init;
|
||||
|
||||
/* Custom metaclass if requested (used for static properties) */
|
||||
if (rec.metaclass) {
|
||||
Py_INCREF(internals.default_metaclass);
|
||||
Py_TYPE(type) = (PyTypeObject *) internals.default_metaclass;
|
||||
}
|
||||
|
||||
/* Supported protocols */
|
||||
type->tp_as_number = &heap_type->as_number;
|
||||
type->tp_as_sequence = &heap_type->as_sequence;
|
||||
type->tp_as_mapping = &heap_type->as_mapping;
|
||||
|
||||
/* Flags */
|
||||
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
type->tp_flags |= Py_TPFLAGS_CHECKTYPES;
|
||||
#endif
|
||||
|
||||
if (rec.dynamic_attr)
|
||||
enable_dynamic_attributes(heap_type);
|
||||
|
||||
if (rec.buffer_protocol)
|
||||
enable_buffer_protocol(heap_type);
|
||||
|
||||
if (PyType_Ready(type) < 0)
|
||||
pybind11_fail(std::string(rec.name) + ": PyType_Ready failed (" + error_string() + ")!");
|
||||
|
||||
assert(rec.dynamic_attr ? PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC)
|
||||
: !PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
|
||||
|
||||
/* Register type with the parent scope */
|
||||
if (rec.scope)
|
||||
setattr(rec.scope, rec.name, (PyObject *) type);
|
||||
|
||||
if (module) // Needed by pydoc
|
||||
setattr((PyObject *) type, "__module__", module);
|
||||
|
||||
return (PyObject *) type;
|
||||
}
|
||||
|
||||
NAMESPACE_END(detail)
|
||||
NAMESPACE_END(pybind11)
|
||||
|
@ -122,7 +122,6 @@
|
||||
#define PYBIND11_SLICE_OBJECT PyObject
|
||||
#define PYBIND11_FROM_STRING PyUnicode_FromString
|
||||
#define PYBIND11_STR_TYPE ::pybind11::str
|
||||
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_base.ob_base.ob_type
|
||||
#define PYBIND11_PLUGIN_IMPL(name) \
|
||||
extern "C" PYBIND11_EXPORT PyObject *PyInit_##name()
|
||||
#else
|
||||
@ -141,7 +140,6 @@
|
||||
#define PYBIND11_SLICE_OBJECT PySliceObject
|
||||
#define PYBIND11_FROM_STRING PyString_FromString
|
||||
#define PYBIND11_STR_TYPE ::pybind11::bytes
|
||||
#define PYBIND11_OB_TYPE(ht_type) (ht_type).ob_type
|
||||
#define PYBIND11_PLUGIN_IMPL(name) \
|
||||
static PyObject *pybind11_init_wrapper(); \
|
||||
extern "C" PYBIND11_EXPORT void init##name() { \
|
||||
@ -363,10 +361,14 @@ struct internals {
|
||||
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions
|
||||
PyTypeObject *static_property_type;
|
||||
PyTypeObject *default_metaclass;
|
||||
std::unordered_map<size_t, PyObject *> bases; // one base type per `instance_size` (very few)
|
||||
#if defined(WITH_THREAD)
|
||||
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
|
||||
PyInterpreterState *istate = nullptr;
|
||||
#endif
|
||||
|
||||
/// Return the appropriate base type for the given instance size
|
||||
PyObject *get_base(size_t instance_size);
|
||||
};
|
||||
|
||||
/// Return a reference to the current 'internals' information
|
||||
|
@ -760,196 +760,39 @@ public:
|
||||
};
|
||||
|
||||
NAMESPACE_BEGIN(detail)
|
||||
extern "C" inline PyObject *get_dict(PyObject *op, void *) {
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(op);
|
||||
if (!dict)
|
||||
dict = PyDict_New();
|
||||
Py_XINCREF(dict);
|
||||
return dict;
|
||||
}
|
||||
|
||||
extern "C" inline int set_dict(PyObject *op, PyObject *new_dict, void *) {
|
||||
if (!PyDict_Check(new_dict)) {
|
||||
PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'",
|
||||
Py_TYPE(new_dict)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(op);
|
||||
Py_INCREF(new_dict);
|
||||
Py_CLEAR(dict);
|
||||
dict = new_dict;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PyGetSetDef generic_getset[] = {
|
||||
{const_cast<char*>("__dict__"), get_dict, set_dict, nullptr, nullptr},
|
||||
{nullptr, nullptr, nullptr, nullptr, nullptr}
|
||||
};
|
||||
|
||||
/// Generic support for creating new Python heap types
|
||||
class generic_type : public object {
|
||||
template <typename...> friend class class_;
|
||||
public:
|
||||
PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check)
|
||||
protected:
|
||||
void initialize(type_record *rec) {
|
||||
auto &internals = get_internals();
|
||||
auto tindex = std::type_index(*(rec->type));
|
||||
void initialize(const type_record &rec) {
|
||||
if (rec.scope && hasattr(rec.scope, rec.name))
|
||||
pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) +
|
||||
"\": an object with that name is already defined");
|
||||
|
||||
if (get_type_info(*(rec->type)))
|
||||
pybind11_fail("generic_type: type \"" + std::string(rec->name) +
|
||||
if (get_type_info(*rec.type))
|
||||
pybind11_fail("generic_type: type \"" + std::string(rec.name) +
|
||||
"\" is already registered!");
|
||||
|
||||
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec->name));
|
||||
object scope_module;
|
||||
if (rec->scope) {
|
||||
if (hasattr(rec->scope, rec->name))
|
||||
pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec->name) +
|
||||
"\": an object with that name is already defined");
|
||||
|
||||
if (hasattr(rec->scope, "__module__")) {
|
||||
scope_module = rec->scope.attr("__module__");
|
||||
} else if (hasattr(rec->scope, "__name__")) {
|
||||
scope_module = rec->scope.attr("__name__");
|
||||
}
|
||||
}
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
|
||||
/* Qualified names for Python >= 3.3 */
|
||||
object scope_qualname;
|
||||
if (rec->scope && hasattr(rec->scope, "__qualname__"))
|
||||
scope_qualname = rec->scope.attr("__qualname__");
|
||||
object ht_qualname;
|
||||
if (scope_qualname)
|
||||
ht_qualname = reinterpret_steal<object>(PyUnicode_FromFormat(
|
||||
"%U.%U", scope_qualname.ptr(), name.ptr()));
|
||||
else
|
||||
ht_qualname = name;
|
||||
#endif
|
||||
|
||||
#if !defined(PYPY_VERSION)
|
||||
std::string full_name = (scope_module ? ((std::string) pybind11::str(scope_module) + "." + rec->name)
|
||||
: std::string(rec->name));
|
||||
#else
|
||||
std::string full_name = std::string(rec->name);
|
||||
#endif
|
||||
|
||||
size_t num_bases = rec->bases.size();
|
||||
auto bases = tuple(rec->bases);
|
||||
|
||||
char *tp_doc = nullptr;
|
||||
if (rec->doc && options::show_user_defined_docstrings()) {
|
||||
/* Allocate memory for docstring (using PyObject_MALLOC, since
|
||||
Python will free this later on) */
|
||||
size_t size = strlen(rec->doc) + 1;
|
||||
tp_doc = (char *) PyObject_MALLOC(size);
|
||||
memcpy((void *) tp_doc, rec->doc, size);
|
||||
}
|
||||
|
||||
/* Danger zone: from now (and until PyType_Ready), make sure to
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
|
||||
auto type_holder = reinterpret_steal<object>(PyType_Type.tp_alloc(&PyType_Type, 0));
|
||||
auto type = (PyHeapTypeObject*) type_holder.ptr();
|
||||
|
||||
if (!type_holder || !name)
|
||||
pybind11_fail(std::string(rec->name) + ": Unable to create type object!");
|
||||
m_ptr = make_new_python_type(rec);
|
||||
|
||||
/* Register supplemental type information in C++ dict */
|
||||
detail::type_info *tinfo = new detail::type_info();
|
||||
tinfo->type = (PyTypeObject *) type;
|
||||
tinfo->type_size = rec->type_size;
|
||||
tinfo->init_holder = rec->init_holder;
|
||||
auto *tinfo = new detail::type_info();
|
||||
tinfo->type = (PyTypeObject *) m_ptr;
|
||||
tinfo->type_size = rec.type_size;
|
||||
tinfo->init_holder = rec.init_holder;
|
||||
tinfo->dealloc = rec.dealloc;
|
||||
|
||||
auto &internals = get_internals();
|
||||
auto tindex = std::type_index(*rec.type);
|
||||
tinfo->direct_conversions = &internals.direct_conversions[tindex];
|
||||
tinfo->default_holder = rec->default_holder;
|
||||
tinfo->default_holder = rec.default_holder;
|
||||
internals.registered_types_cpp[tindex] = tinfo;
|
||||
internals.registered_types_py[type] = tinfo;
|
||||
internals.registered_types_py[m_ptr] = tinfo;
|
||||
|
||||
/* Basic type attributes */
|
||||
type->ht_type.tp_name = strdup(full_name.c_str());
|
||||
type->ht_type.tp_basicsize = (ssize_t) rec->instance_size;
|
||||
|
||||
if (num_bases > 0) {
|
||||
type->ht_type.tp_base = (PyTypeObject *) ((object) bases[0]).inc_ref().ptr();
|
||||
type->ht_type.tp_bases = bases.release().ptr();
|
||||
rec->multiple_inheritance |= num_bases > 1;
|
||||
}
|
||||
|
||||
type->ht_name = name.release().ptr();
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
|
||||
type->ht_qualname = ht_qualname.release().ptr();
|
||||
#endif
|
||||
|
||||
/* Custom metaclass if requested (used for static properties) */
|
||||
if (rec->metaclass)
|
||||
PYBIND11_OB_TYPE(type->ht_type) = internals.default_metaclass;
|
||||
|
||||
/* Supported protocols */
|
||||
type->ht_type.tp_as_number = &type->as_number;
|
||||
type->ht_type.tp_as_sequence = &type->as_sequence;
|
||||
type->ht_type.tp_as_mapping = &type->as_mapping;
|
||||
|
||||
/* Supported elementary operations */
|
||||
type->ht_type.tp_init = (initproc) init;
|
||||
type->ht_type.tp_new = (newfunc) new_instance;
|
||||
type->ht_type.tp_dealloc = rec->dealloc;
|
||||
|
||||
/* Support weak references (needed for the keep_alive feature) */
|
||||
type->ht_type.tp_weaklistoffset = offsetof(instance_essentials<void>, weakrefs);
|
||||
|
||||
/* Flags */
|
||||
type->ht_type.tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
type->ht_type.tp_flags |= Py_TPFLAGS_CHECKTYPES;
|
||||
#endif
|
||||
type->ht_type.tp_flags &= ~Py_TPFLAGS_HAVE_GC;
|
||||
|
||||
/* Support dynamic attributes */
|
||||
if (rec->dynamic_attr) {
|
||||
#if defined(PYPY_VERSION)
|
||||
pybind11_fail(std::string(rec->name) + ": dynamic attributes are "
|
||||
"currently not supported in "
|
||||
"conunction with PyPy!");
|
||||
#endif
|
||||
type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_GC;
|
||||
type->ht_type.tp_dictoffset = type->ht_type.tp_basicsize; // place the dict at the end
|
||||
type->ht_type.tp_basicsize += sizeof(PyObject *); // and allocate enough space for it
|
||||
type->ht_type.tp_getset = generic_getset;
|
||||
type->ht_type.tp_traverse = traverse;
|
||||
type->ht_type.tp_clear = clear;
|
||||
}
|
||||
|
||||
if (rec->buffer_protocol) {
|
||||
type->ht_type.tp_as_buffer = &type->as_buffer;
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
type->ht_type.tp_flags |= Py_TPFLAGS_HAVE_NEWBUFFER;
|
||||
#endif
|
||||
type->as_buffer.bf_getbuffer = getbuffer;
|
||||
type->as_buffer.bf_releasebuffer = releasebuffer;
|
||||
}
|
||||
|
||||
type->ht_type.tp_doc = tp_doc;
|
||||
|
||||
m_ptr = type_holder.ptr();
|
||||
|
||||
if (PyType_Ready(&type->ht_type) < 0)
|
||||
pybind11_fail(std::string(rec->name) + ": PyType_Ready failed (" +
|
||||
detail::error_string() + ")!");
|
||||
|
||||
if (scope_module) // Needed by pydoc
|
||||
attr("__module__") = scope_module;
|
||||
|
||||
/* Register type with the parent scope */
|
||||
if (rec->scope)
|
||||
rec->scope.attr(handle(type->ht_name)) = *this;
|
||||
|
||||
if (rec->multiple_inheritance)
|
||||
mark_parents_nonsimple(&type->ht_type);
|
||||
|
||||
type_holder.release();
|
||||
if (rec.bases.size() > 1 || rec.multiple_inheritance)
|
||||
mark_parents_nonsimple(tinfo->type);
|
||||
}
|
||||
|
||||
/// Helper function which tags all parents of a type using mult. inheritance
|
||||
@ -963,66 +806,6 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
static int init(void *self, PyObject *, PyObject *) {
|
||||
PyTypeObject *type = Py_TYPE(self);
|
||||
std::string msg;
|
||||
#if defined(PYPY_VERSION)
|
||||
msg += handle((PyObject *) type).attr("__module__").cast<std::string>() + ".";
|
||||
#endif
|
||||
msg += type->tp_name;
|
||||
msg += ": No constructor defined!";
|
||||
PyErr_SetString(PyExc_TypeError, msg.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
static PyObject *new_instance(PyTypeObject *type, PyObject *, PyObject *) {
|
||||
instance<void> *self = (instance<void> *) PyType_GenericAlloc((PyTypeObject *) type, 0);
|
||||
auto tinfo = detail::get_type_info(type);
|
||||
self->value = ::operator new(tinfo->type_size);
|
||||
self->owned = true;
|
||||
self->holder_constructed = false;
|
||||
detail::get_internals().registered_instances.emplace(self->value, (PyObject *) self);
|
||||
return (PyObject *) self;
|
||||
}
|
||||
|
||||
static void dealloc(instance<void> *self) {
|
||||
if (self->value) {
|
||||
auto instance_type = Py_TYPE(self);
|
||||
auto ®istered_instances = detail::get_internals().registered_instances;
|
||||
auto range = registered_instances.equal_range(self->value);
|
||||
bool found = false;
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
if (instance_type == Py_TYPE(it->second)) {
|
||||
registered_instances.erase(it);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
pybind11_fail("generic_type::dealloc(): Tried to deallocate unregistered instance!");
|
||||
|
||||
if (self->weakrefs)
|
||||
PyObject_ClearWeakRefs((PyObject *) self);
|
||||
|
||||
PyObject **dict_ptr = _PyObject_GetDictPtr((PyObject *) self);
|
||||
if (dict_ptr)
|
||||
Py_CLEAR(*dict_ptr);
|
||||
}
|
||||
Py_TYPE(self)->tp_free((PyObject*) self);
|
||||
}
|
||||
|
||||
static int traverse(PyObject *op, visitproc visit, void *arg) {
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(op);
|
||||
Py_VISIT(dict);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int clear(PyObject *op) {
|
||||
PyObject *&dict = *_PyObject_GetDictPtr(op);
|
||||
Py_CLEAR(dict);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void install_buffer_funcs(
|
||||
buffer_info *(*get_buffer)(PyObject *, void *),
|
||||
void *get_buffer_data) {
|
||||
@ -1040,37 +823,6 @@ protected:
|
||||
tinfo->get_buffer_data = get_buffer_data;
|
||||
}
|
||||
|
||||
static int getbuffer(PyObject *obj, Py_buffer *view, int flags) {
|
||||
auto tinfo = detail::get_type_info(Py_TYPE(obj));
|
||||
if (view == nullptr || obj == nullptr || !tinfo || !tinfo->get_buffer) {
|
||||
if (view)
|
||||
view->obj = nullptr;
|
||||
PyErr_SetString(PyExc_BufferError, "generic_type::getbuffer(): Internal error");
|
||||
return -1;
|
||||
}
|
||||
memset(view, 0, sizeof(Py_buffer));
|
||||
buffer_info *info = tinfo->get_buffer(obj, tinfo->get_buffer_data);
|
||||
view->obj = obj;
|
||||
view->ndim = 1;
|
||||
view->internal = info;
|
||||
view->buf = info->ptr;
|
||||
view->itemsize = (ssize_t) info->itemsize;
|
||||
view->len = view->itemsize;
|
||||
for (auto s : info->shape)
|
||||
view->len *= s;
|
||||
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
|
||||
view->format = const_cast<char *>(info->format.c_str());
|
||||
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
|
||||
view->ndim = (int) info->ndim;
|
||||
view->strides = (ssize_t *) &info->strides[0];
|
||||
view->shape = (ssize_t *) &info->shape[0];
|
||||
}
|
||||
Py_INCREF(view->obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; }
|
||||
|
||||
void def_property_static_impl(const char *name,
|
||||
handle fget, handle fset,
|
||||
detail::function_record *rec_fget) {
|
||||
@ -1078,7 +830,7 @@ protected:
|
||||
const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings();
|
||||
|
||||
if (is_static) {
|
||||
auto mclass = handle((PyObject *) PYBIND11_OB_TYPE(*((PyTypeObject *) m_ptr)));
|
||||
auto mclass = handle((PyObject *) Py_TYPE(m_ptr));
|
||||
|
||||
if ((PyTypeObject *) mclass.ptr() == &PyType_Type)
|
||||
pybind11_fail(
|
||||
@ -1140,7 +892,7 @@ public:
|
||||
/* Process optional arguments, if any */
|
||||
detail::process_attributes<Extra...>::init(extra..., &record);
|
||||
|
||||
detail::generic_type::initialize(&record);
|
||||
detail::generic_type::initialize(record);
|
||||
|
||||
if (has_alias) {
|
||||
auto &instances = pybind11::detail::get_internals().registered_types_cpp;
|
||||
@ -1352,8 +1104,6 @@ private:
|
||||
inst->holder.~holder_type();
|
||||
else if (inst->owned)
|
||||
::operator delete(inst->value);
|
||||
|
||||
generic_type::dealloc((detail::instance<void> *) inst);
|
||||
}
|
||||
|
||||
static detail::function_record *get_function_record(handle h) {
|
||||
|
@ -36,6 +36,10 @@ public:
|
||||
Hamster(const std::string &name) : Pet(name, "rodent") {}
|
||||
};
|
||||
|
||||
class Chimera : public Pet {
|
||||
Chimera() : Pet("Kimmy", "chimera") {}
|
||||
};
|
||||
|
||||
std::string pet_name_species(const Pet &pet) {
|
||||
return pet.name() + " is a " + pet.species();
|
||||
}
|
||||
@ -74,6 +78,8 @@ test_initializer inheritance([](py::module &m) {
|
||||
py::class_<Hamster, Pet>(m, "Hamster")
|
||||
.def(py::init<std::string>());
|
||||
|
||||
py::class_<Chimera, Pet>(m, "Chimera");
|
||||
|
||||
m.def("pet_name_species", pet_name_species);
|
||||
m.def("dog_bark", dog_bark);
|
||||
|
||||
|
@ -2,7 +2,7 @@ import pytest
|
||||
|
||||
|
||||
def test_inheritance(msg):
|
||||
from pybind11_tests import Pet, Dog, Rabbit, Hamster, dog_bark, pet_name_species
|
||||
from pybind11_tests import Pet, Dog, Rabbit, Hamster, Chimera, dog_bark, pet_name_species
|
||||
|
||||
roger = Rabbit('Rabbit')
|
||||
assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot"
|
||||
@ -30,6 +30,10 @@ def test_inheritance(msg):
|
||||
Invoked with: <m.Pet object at 0>
|
||||
"""
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
Chimera("lion", "goat")
|
||||
assert "No constructor defined!" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_automatic_upcasting():
|
||||
from pybind11_tests import return_class_1, return_class_2, return_class_n, return_none
|
||||
|
@ -81,3 +81,73 @@ test_initializer multiple_inheritance_nonexplicit([](py::module &m) {
|
||||
m.def("bar_base2a", [](Base2a *b) { return b->bar(); });
|
||||
m.def("bar_base2a_sharedptr", [](std::shared_ptr<Base2a> b) { return b->bar(); });
|
||||
});
|
||||
|
||||
struct Vanilla {
|
||||
std::string vanilla() { return "Vanilla"; };
|
||||
};
|
||||
|
||||
struct WithStatic1 {
|
||||
static std::string static_func1() { return "WithStatic1"; };
|
||||
static int static_value1;
|
||||
};
|
||||
|
||||
struct WithStatic2 {
|
||||
static std::string static_func2() { return "WithStatic2"; };
|
||||
static int static_value2;
|
||||
};
|
||||
|
||||
struct WithDict { };
|
||||
|
||||
struct VanillaStaticMix1 : Vanilla, WithStatic1, WithStatic2 {
|
||||
static std::string static_func() { return "VanillaStaticMix1"; }
|
||||
static int static_value;
|
||||
};
|
||||
|
||||
struct VanillaStaticMix2 : WithStatic1, Vanilla, WithStatic2 {
|
||||
static std::string static_func() { return "VanillaStaticMix2"; }
|
||||
static int static_value;
|
||||
};
|
||||
|
||||
struct VanillaDictMix1 : Vanilla, WithDict { };
|
||||
struct VanillaDictMix2 : WithDict, Vanilla { };
|
||||
|
||||
int WithStatic1::static_value1 = 1;
|
||||
int WithStatic2::static_value2 = 2;
|
||||
int VanillaStaticMix1::static_value = 12;
|
||||
int VanillaStaticMix2::static_value = 12;
|
||||
|
||||
test_initializer mi_static_properties([](py::module &pm) {
|
||||
auto m = pm.def_submodule("mi");
|
||||
|
||||
py::class_<Vanilla>(m, "Vanilla")
|
||||
.def(py::init<>())
|
||||
.def("vanilla", &Vanilla::vanilla);
|
||||
|
||||
py::class_<WithStatic1>(m, "WithStatic1", py::metaclass())
|
||||
.def(py::init<>())
|
||||
.def_static("static_func1", &WithStatic1::static_func1)
|
||||
.def_readwrite_static("static_value1", &WithStatic1::static_value1);
|
||||
|
||||
py::class_<WithStatic2>(m, "WithStatic2", py::metaclass())
|
||||
.def(py::init<>())
|
||||
.def_static("static_func2", &WithStatic2::static_func2)
|
||||
.def_readwrite_static("static_value2", &WithStatic2::static_value2);
|
||||
|
||||
py::class_<VanillaStaticMix1, Vanilla, WithStatic1, WithStatic2>(
|
||||
m, "VanillaStaticMix1", py::metaclass())
|
||||
.def(py::init<>())
|
||||
.def_static("static_func", &VanillaStaticMix1::static_func)
|
||||
.def_readwrite_static("static_value", &VanillaStaticMix1::static_value);
|
||||
|
||||
py::class_<VanillaStaticMix2, WithStatic1, Vanilla, WithStatic2>(
|
||||
m, "VanillaStaticMix2", py::metaclass())
|
||||
.def(py::init<>())
|
||||
.def_static("static_func", &VanillaStaticMix2::static_func)
|
||||
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value);
|
||||
|
||||
#if !defined(PYPY_VERSION)
|
||||
py::class_<WithDict>(m, "WithDict", py::dynamic_attr()).def(py::init<>());
|
||||
py::class_<VanillaDictMix1, Vanilla, WithDict>(m, "VanillaDictMix1").def(py::init<>());
|
||||
py::class_<VanillaDictMix2, WithDict, Vanilla>(m, "VanillaDictMix2").def(py::init<>());
|
||||
#endif
|
||||
});
|
||||
|
@ -1,3 +1,6 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def test_multiple_inheritance_cpp():
|
||||
from pybind11_tests import MIType
|
||||
|
||||
@ -49,6 +52,17 @@ def test_multiple_inheritance_mix2():
|
||||
assert mt.bar() == 4
|
||||
|
||||
|
||||
def test_multiple_inheritance_error():
|
||||
"""Inheriting from multiple C++ bases in Python is not supported"""
|
||||
from pybind11_tests import Base1, Base2
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
# noinspection PyUnusedLocal
|
||||
class MI(Base1, Base2):
|
||||
pass
|
||||
assert "Can't inherit from multiple C++ classes in Python" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_multiple_inheritance_virtbase():
|
||||
from pybind11_tests import Base12a, bar_base2a, bar_base2a_sharedptr
|
||||
|
||||
@ -60,3 +74,38 @@ def test_multiple_inheritance_virtbase():
|
||||
assert mt.bar() == 4
|
||||
assert bar_base2a(mt) == 4
|
||||
assert bar_base2a_sharedptr(mt) == 4
|
||||
|
||||
|
||||
def test_mi_static_properties():
|
||||
"""Mixing bases with and without static properties should be possible
|
||||
and the result should be independent of base definition order"""
|
||||
from pybind11_tests import mi
|
||||
|
||||
for d in (mi.VanillaStaticMix1(), mi.VanillaStaticMix2()):
|
||||
assert d.vanilla() == "Vanilla"
|
||||
assert d.static_func1() == "WithStatic1"
|
||||
assert d.static_func2() == "WithStatic2"
|
||||
assert d.static_func() == d.__class__.__name__
|
||||
|
||||
mi.WithStatic1.static_value1 = 1
|
||||
mi.WithStatic2.static_value2 = 2
|
||||
assert d.static_value1 == 1
|
||||
assert d.static_value2 == 2
|
||||
assert d.static_value == 12
|
||||
|
||||
d.static_value1 = 0
|
||||
assert d.static_value1 == 0
|
||||
d.static_value2 = 0
|
||||
assert d.static_value2 == 0
|
||||
d.static_value = 0
|
||||
assert d.static_value == 0
|
||||
|
||||
|
||||
@pytest.unsupported_on_pypy
|
||||
def test_mi_dynamic_attributes():
|
||||
"""Mixing bases with and without dynamic attribute support"""
|
||||
from pybind11_tests import mi
|
||||
|
||||
for d in (mi.VanillaDictMix1(), mi.VanillaDictMix2()):
|
||||
d.dynamic = 1
|
||||
assert d.dynamic == 1
|
||||
|
Loading…
Reference in New Issue
Block a user