From c0c5ec2bd845676eabe055d734960cc9b9ac5ac5 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 22 Nov 2016 15:59:59 +0100 Subject: [PATCH 01/14] WIP: PyPy support This commit includes a few minor modifications to pybind11 that are needed to get simple hello-world style functions to compile and run on the latest PyPy. Sadly, more complex things are still broken: for instance, creating new types fails with the error message 'TypeError: can't set attributes on type object <..>' when pybind11 tries to set the __module__ attribute. Digging into the pip codebase indicates that it believes that the underlying type is not a heap type, which is incorrect. So this is likely a PyPy bug. --- include/pybind11/cast.h | 18 +++++++++++------- include/pybind11/pybind11.h | 12 ++++++++---- include/pybind11/pytypes.h | 4 ++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 8e9559cedc..be88728b7f 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -141,6 +141,7 @@ PYBIND11_NOINLINE inline std::string error_string() { PyException_SetTraceback(scope.value, scope.trace); #endif +#if !defined(PYPY_VERSION) if (scope.trace) { PyTracebackObject *trace = (PyTracebackObject *) scope.trace; @@ -160,6 +161,7 @@ PYBIND11_NOINLINE inline std::string error_string() { } trace = trace->tb_next; } +#endif return errorString; } @@ -176,7 +178,9 @@ PYBIND11_NOINLINE inline handle get_object_handle(const void *ptr, const detail: } inline PyThreadState *get_thread_state_unchecked() { -#if PY_VERSION_HEX < 0x03000000 +#if defined(PYPY_VERSION) + return PyThreadState_GET(); +#elif PY_VERSION_HEX < 0x03000000 return _PyThreadState_Current; #elif PY_VERSION_HEX < 0x03050000 return (PyThreadState*) _Py_atomic_load_relaxed(&_PyThreadState_Current); @@ -224,7 +228,7 @@ class type_caster_generic { /* If this is a python class, also check the parents recursively */ auto const &type_dict = get_internals().registered_types_py; - bool new_style_class = PyType_Check(tobj); + bool new_style_class = PyType_Check((PyObject *) tobj); if (type_dict.find(tobj) == type_dict.end() && new_style_class && tobj->tp_bases) { auto parents = reinterpret_borrow(tobj->tp_bases); for (handle parent : parents) { @@ -662,10 +666,10 @@ template <> class type_caster { #if PY_MAJOR_VERSION >= 3 buffer = PyUnicode_AsWideCharString(load_src.ptr(), &length); #else - temp = reinterpret_steal( - sizeof(wchar_t) == sizeof(short) - ? PyUnicode_AsUTF16String(load_src.ptr()) - : PyUnicode_AsUTF32String(load_src.ptr())); + temp = reinterpret_steal(PyUnicode_AsEncodedString( + load_src.ptr(), sizeof(wchar_t) == sizeof(short) + ? "utf16" : "utf32", nullptr)); + if (temp) { int err = PYBIND11_BYTES_AS_STRING_AND_SIZE(temp.ptr(), (char **) &buffer, &length); if (err == -1) { buffer = nullptr; } // TypeError @@ -868,7 +872,7 @@ template class type_caster_holder : public /* If this is a python class, also check the parents recursively */ auto const &type_dict = get_internals().registered_types_py; - bool new_style_class = PyType_Check(tobj); + bool new_style_class = PyType_Check((PyObject *) tobj); if (type_dict.find(tobj) == type_dict.end() && new_style_class && tobj->tp_bases) { auto parents = reinterpret_borrow(tobj->tp_bases); for (handle parent : parents) { diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index e012895893..13f0470728 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -261,7 +261,7 @@ class cpp_function : public function { detail::function_record *chain = nullptr, *chain_start = rec; if (rec->sibling) { if (PyCFunction_Check(rec->sibling.ptr())) { - auto rec_capsule = reinterpret_borrow(PyCFunction_GetSelf(rec->sibling.ptr())); + auto rec_capsule = reinterpret_borrow(PyCFunction_GET_SELF(rec->sibling.ptr())); chain = (detail::function_record *) rec_capsule; /* Never append a method to an overload chain of a parent class; instead, hide the parent's overloads in this case */ @@ -1185,7 +1185,7 @@ class class_ : public detail::generic_type { static detail::function_record *get_function_record(handle h) { h = detail::get_function(h); - return h ? (detail::function_record *) reinterpret_borrow(PyCFunction_GetSelf(h.ptr())) + return h ? (detail::function_record *) reinterpret_borrow(PyCFunction_GET_SELF(h.ptr())) : nullptr; } }; @@ -1521,7 +1521,7 @@ void print(Args &&...args) { detail::print(c.args(), c.kwargs()); } -#if defined(WITH_THREAD) +#if defined(WITH_THREAD) && !defined(PYPY_VERSION) /* The functions below essentially reproduce the PyGILState_* API using a RAII * pattern, but there are a few important differences: @@ -1676,7 +1676,9 @@ inline function get_type_overload(const void *this_ptr, const detail::type_info return function(); } - /* Don't call dispatch code if invoked from overridden function */ + /* Don't call dispatch code if invoked from overridden function. + Unfortunately this doesn't work on PyPy. */ +#if !defined(PYPY_VERSION) PyFrameObject *frame = PyThreadState_Get()->frame; if (frame && (std::string) str(frame->f_code->co_name) == name && frame->f_code->co_argcount > 0) { @@ -1686,6 +1688,8 @@ inline function get_type_overload(const void *this_ptr, const detail::type_info if (self_caller == py_object.ptr()) return function(); } +#endif + return overload; } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 9753958664..5d84e7a6b3 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -693,6 +693,10 @@ class float_ : public object { operator double() const { return (double) PyFloat_AsDouble(m_ptr); } }; +#if defined(PYPY_VERSION) +inline bool PyWeakref_Check(PyObject *obj); +#endif + class weakref : public object { public: PYBIND11_OBJECT_DEFAULT(weakref, object, PyWeakref_Check) From cbf3f19b19625092869e1841013dfc620e89afb0 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Thu, 24 Nov 2016 01:14:41 +0100 Subject: [PATCH 02/14] full test suite compiles with PyPy now, crash in PyIter_Next --- include/pybind11/eval.h | 7 +++++++ include/pybind11/functional.h | 2 +- include/pybind11/pybind11.h | 21 +++++++++++++++++++-- include/pybind11/stl.h | 4 ++-- tests/test_multiple_inheritance.cpp | 3 +++ 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/include/pybind11/eval.h b/include/pybind11/eval.h index 204427d779..5b2b98272d 100644 --- a/include/pybind11/eval.h +++ b/include/pybind11/eval.h @@ -95,8 +95,15 @@ object eval_file(str fname, object global = object(), object local = object()) { pybind11_fail("File \"" + fname_str + "\" could not be opened!"); } +#if PY_VERSION_HEX < 0x03000000 && defined(PYPY_VERSION) + PyObject *result = PyRun_File(f, fname_str.c_str(), start, global.ptr(), + local.ptr()); + (void) closeFile; +#else PyObject *result = PyRun_FileEx(f, fname_str.c_str(), start, global.ptr(), local.ptr(), closeFile); +#endif + if (!result) throw error_already_set(); return reinterpret_steal(result); diff --git a/include/pybind11/functional.h b/include/pybind11/functional.h index 7f1ffc1d7c..2dd52d3ecc 100644 --- a/include/pybind11/functional.h +++ b/include/pybind11/functional.h @@ -39,7 +39,7 @@ struct type_caster> { captured variables), in which case the roundtrip can be avoided. */ if (PyCFunction_Check(src_.ptr())) { - auto c = reinterpret_borrow(PyCFunction_GetSelf(src_.ptr())); + auto c = reinterpret_borrow(PyCFunction_GET_SELF(src_.ptr())); auto rec = (function_record *) c; if (rec && rec->is_stateless && rec->data[1] == &typeid(function_type)) { diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 13f0470728..b182109d55 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -568,7 +568,7 @@ class module : public object { } module def_submodule(const char *name, const char *doc = nullptr) { - std::string full_name = std::string(PyModule_GetName(m_ptr)) + std::string full_name = attr("__name__").cast() + std::string(".") + std::string(name); auto result = reinterpret_borrow(PyImport_AddModule(full_name.c_str())); if (doc && options::show_user_defined_docstrings()) @@ -1258,9 +1258,12 @@ template class enum_ : public class_ { PyObject *dict = ((PyTypeObject *) this->m_ptr)->tp_dict; PyObject *key, *value; ssize_t pos = 0; - while (PyDict_Next(dict, &pos, &key, &value)) + + while (PyDict_Next(dict, &pos, &key, &value)) { if (PyObject_IsInstance(value, this->m_ptr)) m_parent.attr(key) = value; + } + return *this; } @@ -1644,6 +1647,20 @@ class gil_scoped_release { PyThreadState *tstate; bool disassoc; }; +#elif defined(PYPY_VERSION) +class gil_scoped_acquire { + PyGILState_STATE state; +public: + gil_scoped_acquire() { state = PyGILState_Ensure(); } + ~gil_scoped_acquire() { PyGILState_Release(state); } +}; + +class gil_scoped_release { + PyThreadState *state; +public: + gil_scoped_release() { state = PyEval_SaveThread(); } + ~gil_scoped_release() { PyEval_RestoreThread(state); } +}; #else class gil_scoped_acquire { }; class gil_scoped_release { }; diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index d4b0fc914a..4b557bd16f 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -139,7 +139,7 @@ template struct list_caster { auto value_ = reinterpret_steal(value_conv::cast(value, policy, parent)); if (!value_) return handle(); - PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference + PyList_SET_ITEM(l.ptr(), (ssize_t) index++, value_.release().ptr()); // steals a reference } return l.release(); } @@ -192,7 +192,7 @@ template s auto value_ = reinterpret_steal(value_conv::cast(value, policy, parent)); if (!value_) return handle(); - PyList_SET_ITEM(l.ptr(), index++, value_.release().ptr()); // steals a reference + PyList_SET_ITEM(l.ptr(), (ssize_t) index++, value_.release().ptr()); // steals a reference } return l.release(); } diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index 3cb12b68d6..5b184a872a 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -10,6 +10,7 @@ #include "pybind11_tests.h" +#if !defined(PYPY_VERSION) struct Base1 { Base1(int i) : i(i) { } @@ -82,3 +83,5 @@ 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 b) { return b->bar(); }); }); + +#endif From 69988cbb3f7efa7b8950ad7fe1a3f298538904ac Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 2 Dec 2016 17:29:10 +0100 Subject: [PATCH 03/14] pypy: enum_ fixes --- include/pybind11/pybind11.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index b182109d55..58e1752902 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1255,6 +1255,7 @@ template class enum_ : public class_ { /// Export enumeration entries into the parent scope enum_ &export_values() { +#if !defined(PYPY_VERSION) PyObject *dict = ((PyTypeObject *) this->m_ptr)->tp_dict; PyObject *key, *value; ssize_t pos = 0; @@ -1263,6 +1264,19 @@ template class enum_ : public class_ { if (PyObject_IsInstance(value, this->m_ptr)) m_parent.attr(key) = value; } +#else + /* PyPy's cpyext still has difficulties with the above + CPython API calls; emulate using Python code. */ + dict d; d["t"] = *this; d["p"] = m_parent; + PyObject *result = PyRun_String( + "for k, v in t.__dict__.items():\n" + " if isinstance(v, t):\n" + " setattr(p, k, v)\n", + Py_file_input, d.ptr(), d.ptr()); + if (result == nullptr) + throw error_already_set(); + Py_DECREF(result); +#endif return *this; } From 98e7d923f7f517017dd069db5f3e41e733c97654 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 2 Dec 2016 17:29:40 +0100 Subject: [PATCH 04/14] add isinstance(handle, handle) convenience function (unrelated to pypy changes) --- include/pybind11/cast.h | 8 ++------ include/pybind11/pytypes.h | 7 +++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index be88728b7f..c4d78f5ab7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -108,14 +108,10 @@ PYBIND11_NOINLINE inline handle get_type_handle(const std::type_info &tp, bool t } PYBIND11_NOINLINE inline bool isinstance_generic(handle obj, const std::type_info &tp) { - const auto type = detail::get_type_handle(tp, false); + handle type = detail::get_type_handle(tp, false); if (!type) return false; - - const auto result = PyObject_IsInstance(obj.ptr(), type.ptr()); - if (result == -1) - throw error_already_set(); - return result != 0; + return isinstance(obj, type); } PYBIND11_NOINLINE inline std::string error_string() { diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 5d84e7a6b3..eaefd13d4a 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -165,6 +165,13 @@ bool isinstance(handle obj) { return detail::isinstance_generic(obj, typeid(T)); template <> inline bool isinstance(handle obj) = delete; template <> inline bool isinstance(handle obj) { return obj.ptr() != nullptr; } +inline bool isinstance(handle obj, handle type) { + const auto result = PyObject_IsInstance(obj.ptr(), type.ptr()); + if (result == -1) + throw error_already_set(); + return result != 0; +} + inline bool hasattr(handle obj, handle name) { return PyObject_HasAttr(obj.ptr(), name.ptr()) == 1; } From e3bedfec9b086fc00ce1700d5cb22206da07f5f6 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Sat, 3 Dec 2016 01:04:18 +0100 Subject: [PATCH 05/14] Got 95% of testcases to pass on PyPy --- include/pybind11/attr.h | 30 ++++- include/pybind11/pybind11.h | 182 +++++++++++++++++--------- tests/conftest.py | 3 + tests/constructor_stats.h | 33 ++++- tests/test_alias_initialization.py | 20 ++- tests/test_buffers.cpp | 2 +- tests/test_buffers.py | 50 +++---- tests/test_inheritance.py | 1 + tests/test_keep_alive.py | 29 ++-- tests/test_methods_and_attributes.cpp | 7 +- tests/test_methods_and_attributes.py | 15 ++- tests/test_multiple_inheritance.py | 5 + tests/test_numpy_array.py | 2 + tests/test_operator_overloading.py | 1 - tests/test_pickling.py | 1 + tests/test_python_types.cpp | 5 +- tests/test_python_types.py | 7 +- tests/test_virtual_functions.py | 3 + 18 files changed, 274 insertions(+), 122 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 448612c52c..0676d5da6b 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -47,6 +47,12 @@ struct multiple_inheritance { }; /// Annotation which enables dynamic attributes, i.e. adds `__dict__` to a class struct dynamic_attr { }; +/// Annotation which enables the buffer protocol for a type +struct buffer_protocol { }; + +/// Annotation which requests that a special metaclass is created for a type +struct metaclass { }; + /// Annotation to mark enums as an arithmetic type struct arithmetic { }; @@ -136,7 +142,9 @@ struct function_record { /// Special data structure which (temporarily) holds metadata about a bound class struct type_record { - PYBIND11_NOINLINE type_record() { } + PYBIND11_NOINLINE type_record() + : multiple_inheritance(false), dynamic_attr(false), + buffer_protocol(false), metaclass(false) { } /// Handle to the parent scope handle scope; @@ -166,10 +174,16 @@ struct type_record { const char *doc = nullptr; /// Multiple inheritance marker - bool multiple_inheritance = false; + bool multiple_inheritance : 1; /// Does the class manage a __dict__? - bool dynamic_attr = false; + bool dynamic_attr : 1; + + /// Does the class implement the buffer protocol? + bool buffer_protocol : 1; + + /// Does the class require its own metaclass? + bool metaclass : 1; PYBIND11_NOINLINE void add_base(const std::type_info *base, void *(*caster)(void *)) { auto base_info = detail::get_type_info(*base, false); @@ -309,6 +323,16 @@ struct process_attribute : process_attribute_default static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; } }; +template <> +struct process_attribute : process_attribute_default { + static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; } +}; + +template <> +struct process_attribute : process_attribute_default { + static void init(const metaclass &, type_record *r) { r->metaclass = true; } +}; + /// Process an 'arithmetic' attribute for enums (does nothing here) template <> diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 58e1752902..d170b2d39f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -221,6 +221,11 @@ class cpp_function : public function { if (!t) pybind11_fail("Internal error while parsing type signature (1)"); if (auto tinfo = detail::get_type_info(*t)) { +#if defined(PYPY_VERSION) + signature += handle((PyObject *) tinfo->type) + .attr("__module__") + .cast() + "."; +#endif signature += tinfo->type->tp_name; } else { std::string tname(t->name()); @@ -568,7 +573,7 @@ class module : public object { } module def_submodule(const char *name, const char *doc = nullptr) { - std::string full_name = attr("__name__").cast() + std::string full_name = std::string(PyModule_GetName(m_ptr)) + std::string(".") + std::string(name); auto result = reinterpret_borrow(PyImport_AddModule(full_name.c_str())); if (doc && options::show_user_defined_docstrings()) @@ -660,20 +665,57 @@ class generic_type : public object { object scope_qualname; if (rec->scope && hasattr(rec->scope, "__qualname__")) scope_qualname = rec->scope.attr("__qualname__"); - object ht_qualname; - if (scope_qualname) { + object ht_qualname, ht_qualname_meta; + if (scope_qualname) ht_qualname = reinterpret_steal(PyUnicode_FromFormat( "%U.%U", scope_qualname.ptr(), name.ptr())); - } else { + else ht_qualname = name; - } + if (rec->metaclass) + ht_qualname_meta = reinterpret_steal( + PyUnicode_FromFormat("%U__Meta", ht_qualname.ptr())); #endif - size_t num_bases = rec->bases.size(); - auto bases = tuple(rec->bases); - +#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 + + /* Create a custom metaclass if requested (used for static properties) */ + object metaclass; + if (rec->metaclass) { + std::string name_ = full_name + "__Meta"; + object name = reinterpret_steal(PYBIND11_FROM_STRING(name_.c_str())); + metaclass = reinterpret_steal(PyType_Type.tp_alloc(&PyType_Type, 0)); + if (!metaclass || !name) + pybind11_fail("generic_type::generic_type(): unable to create metaclass!"); + + /* 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 = (PyHeapTypeObject*) metaclass.ptr(); + type->ht_name = name.release().ptr(); + + +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 + /* Qualified names for Python >= 3.3 */ + type->ht_qualname = ht_qualname.release().ptr(); +#endif + type->ht_type.tp_name = strdup(name_.c_str()); + type->ht_type.tp_base = &PyType_Type; + type->ht_type.tp_flags |= (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE) & + ~Py_TPFLAGS_HAVE_GC; + + if (PyType_Ready(&type->ht_type) < 0) + pybind11_fail("generic_type::generic_type(): failure in PyType_Ready() for metaclass!"); + } + + size_t num_bases = rec->bases.size(); + auto bases = tuple(rec->bases); char *tp_doc = nullptr; if (rec->doc && options::show_user_defined_docstrings()) { @@ -720,6 +762,9 @@ class generic_type : public object { type->ht_qualname = ht_qualname.release().ptr(); #endif + /* Metaclass */ + PYBIND11_OB_TYPE(type->ht_type) = (PyTypeObject *) metaclass.release().ptr(); + /* Supported protocols */ type->ht_type.tp_as_number = &type->as_number; type->ht_type.tp_as_sequence = &type->as_sequence; @@ -750,14 +795,23 @@ class generic_type : public object { 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() + ")!"); - m_ptr = type_holder.ptr(); - if (scope_module) // Needed by pydoc attr("__module__") = scope_module; @@ -782,43 +836,14 @@ class generic_type : public object { } } - /// Allocate a metaclass on demand (for static properties) - handle metaclass() { - auto &ht_type = ((PyHeapTypeObject *) m_ptr)->ht_type; - auto &ob_type = PYBIND11_OB_TYPE(ht_type); - - if (ob_type == &PyType_Type) { - std::string name_ = std::string(ht_type.tp_name) + "__Meta"; -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - auto ht_qualname = reinterpret_steal(PyUnicode_FromFormat("%U__Meta", attr("__qualname__").ptr())); -#endif - auto name = reinterpret_steal(PYBIND11_FROM_STRING(name_.c_str())); - auto type_holder = reinterpret_steal(PyType_Type.tp_alloc(&PyType_Type, 0)); - if (!type_holder || !name) - pybind11_fail("generic_type::metaclass(): unable to create type object!"); - - auto type = (PyHeapTypeObject*) type_holder.ptr(); - type->ht_name = name.release().ptr(); - -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 - /* Qualified names for Python >= 3.3 */ - type->ht_qualname = ht_qualname.release().ptr(); -#endif - type->ht_type.tp_name = strdup(name_.c_str()); - type->ht_type.tp_base = ob_type; - type->ht_type.tp_flags |= (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE) & - ~Py_TPFLAGS_HAVE_GC; - - if (PyType_Ready(&type->ht_type) < 0) - pybind11_fail("generic_type::metaclass(): PyType_Ready failed!"); - - ob_type = (PyTypeObject *) type_holder.release().ptr(); - } - return handle((PyObject *) ob_type); - } - static int init(void *self, PyObject *, PyObject *) { - std::string msg = std::string(Py_TYPE(self)->tp_name) + ": No constructor defined!"; + PyTypeObject *type = Py_TYPE(self); + std::string msg; +#if defined(PYPY_VERSION) + msg += handle((PyObject *) type).attr("__module__").cast() + "."; +#endif + msg += type->tp_name; + msg += ": No constructor defined!"; PyErr_SetString(PyExc_TypeError, msg.c_str()); return -1; } @@ -876,13 +901,13 @@ class generic_type : public object { buffer_info *(*get_buffer)(PyObject *, void *), void *get_buffer_data) { PyHeapTypeObject *type = (PyHeapTypeObject*) m_ptr; - 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; auto tinfo = detail::get_type_info(&type->ht_type); + if ((type->ht_type.tp_flags & Py_TPFLAGS_HAVE_NEWBUFFER) == 0) + pybind11_fail( + "To be able to register buffer protocol support for the type '" + + std::string(tinfo->type->tp_name) + + "' the associated class<>(..) invocation must " + "include the pybind11::buffer_protocol() annotation!"); tinfo->get_buffer = get_buffer; tinfo->get_buffer_data = get_buffer_data; } @@ -890,6 +915,8 @@ class generic_type : public object { 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; } @@ -1118,14 +1145,26 @@ class class_ : public detail::generic_type { rec_fset->doc = strdup(rec_fset->doc); } } - pybind11::str doc_obj = pybind11::str((rec_fget->doc && pybind11::options::show_user_defined_docstrings()) ? rec_fget->doc : ""); + pybind11::str doc_obj = pybind11::str( + (rec_fget->doc && pybind11::options::show_user_defined_docstrings()) + ? rec_fget->doc : ""); const auto property = reinterpret_steal( PyObject_CallFunctionObjArgs((PyObject *) &PyProperty_Type, fget.ptr() ? fget.ptr() : Py_None, fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr(), nullptr)); - if (rec_fget->is_method && rec_fget->scope) + if (rec_fget->is_method && rec_fget->scope) { attr(name) = property; - else - metaclass().attr(name) = property; + } else { + auto mclass = handle((PyObject *) PYBIND11_OB_TYPE(*((PyTypeObject *) m_ptr))); + + if ((PyTypeObject *) mclass.ptr() == &PyType_Type) + pybind11_fail( + "Adding static properties to the type '" + + std::string(((PyTypeObject *) m_ptr)->tp_name) + + "' requires the type to have a custom metaclass. Please " + "ensure that one is created by supplying the pybind11::metaclass() " + "annotation to the associated class_<>(..) invocation."); + mclass.attr(name) = property; + } return *this; } @@ -1689,10 +1728,10 @@ error_already_set::~error_already_set() { } inline function get_type_overload(const void *this_ptr, const detail::type_info *this_type, const char *name) { - handle py_object = detail::get_object_handle(this_ptr, this_type); - if (!py_object) + handle self = detail::get_object_handle(this_ptr, this_type); + if (!self) return function(); - handle type = py_object.get_type(); + handle type = self.get_type(); auto key = std::make_pair(type.ptr(), name); /* Cache functions that aren't overloaded in Python to avoid @@ -1701,7 +1740,7 @@ inline function get_type_overload(const void *this_ptr, const detail::type_info if (cache.find(key) != cache.end()) return function(); - function overload = getattr(py_object, name, function()); + function overload = getattr(self, name, function()); if (overload.is_cpp_function()) { cache.insert(key); return function(); @@ -1716,9 +1755,30 @@ inline function get_type_overload(const void *this_ptr, const detail::type_info PyFrame_FastToLocals(frame); PyObject *self_caller = PyDict_GetItem( frame->f_locals, PyTuple_GET_ITEM(frame->f_code->co_varnames, 0)); - if (self_caller == py_object.ptr()) + if (self_caller == self.ptr()) return function(); } +#else + /* PyPy currently doesn't provide a detailed cpyext emulation of + frame objects, so we have to emulate this using Python. This + is going to be slow..*/ + dict d; d["self"] = self; d["name"] = pybind11::str(name); + PyObject *result = PyRun_String( + "import inspect\n" + "frame = inspect.currentframe()\n" + "if frame is not None:\n" + " frame = frame.f_back\n" + " if frame is not None and str(frame.f_code.co_name) == name and " + "frame.f_code.co_argcount > 0:\n" + " self_caller = frame.f_locals[frame.f_code.co_varnames[0]]\n" + " if self_caller == self:\n" + " self = None\n", + Py_file_input, d.ptr(), d.ptr()); + if (result == nullptr) + throw error_already_set(); + if ((handle) d["self"] == Py_None) + return function(); + Py_DECREF(result); #endif return overload; diff --git a/tests/conftest.py b/tests/conftest.py index d4335fc6d3..6237bf499d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,6 +10,7 @@ import re import sys import contextlib +import platform _unicode_marker = re.compile(r'u(\'[^\']*\')') _long_marker = re.compile(r'([0-9])L') @@ -190,6 +191,7 @@ def pytest_namespace(): from pybind11_tests import have_eigen except ImportError: have_eigen = False + pypy = platform.python_implementation() == "PyPy" skipif = pytest.mark.skipif return { @@ -200,6 +202,7 @@ def pytest_namespace(): reason="eigen and/or numpy are not installed"), 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, reason="eigen and/or scipy are not installed"), + 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy") } diff --git a/tests/constructor_stats.h b/tests/constructor_stats.h index eb3e49cabf..9ef3248408 100644 --- a/tests/constructor_stats.h +++ b/tests/constructor_stats.h @@ -85,27 +85,51 @@ class ConstructorStats { created(inst); copy_constructions++; } + void move_created(void *inst) { created(inst); move_constructions++; } + void default_created(void *inst) { created(inst); default_constructions++; } + void created(void *inst) { ++_instances[inst]; - }; + } + void destroyed(void *inst) { if (--_instances[inst] < 0) - throw std::runtime_error("cstats.destroyed() called with unknown instance; potential double-destruction or a missing cstats.created()"); + throw std::runtime_error("cstats.destroyed() called with unknown " + "instance; potential double-destruction " + "or a missing cstats.created()"); } - int alive() { + static void gc() { // Force garbage collection to ensure any pending destructors are invoked: +#if defined(PYPY_VERSION) + PyObject *globals = PyEval_GetGlobals(); + PyObject *result = PyRun_String( + "import gc\n" + "for i in range(2):" + " gc.collect()\n", + Py_file_input, globals, globals); + if (result == nullptr) + throw py::error_already_set(); + Py_DECREF(result); +#else py::module::import("gc").attr("collect")(); +#endif + } + + int alive() { + gc(); int total = 0; - for (const auto &p : _instances) if (p.second > 0) total += p.second; + for (const auto &p : _instances) + if (p.second > 0) + total += p.second; return total; } @@ -134,6 +158,7 @@ class ConstructorStats { // Gets constructor stats from a C++ type template static ConstructorStats& get() { + gc(); return get(typeid(T)); } diff --git a/tests/test_alias_initialization.py b/tests/test_alias_initialization.py index 0ed9d2f798..0f3463ace7 100644 --- a/tests/test_alias_initialization.py +++ b/tests/test_alias_initialization.py @@ -1,10 +1,16 @@ import gc +def collect(): + gc.collect() + gc.collect() + + def test_alias_delay_initialization1(capture): - """A only initializes its trampoline class when we inherit from it; if we just - create and use an A instance directly, the trampoline initialization is bypassed - and we only initialize an A() instead (for performance reasons). + """ + A only initializes its trampoline class when we inherit from it; if we just + create and use an A instance directly, the trampoline initialization is + bypassed and we only initialize an A() instead (for performance reasons). """ from pybind11_tests import A, call_f @@ -20,7 +26,7 @@ def f(self): a = A() call_f(a) del a - gc.collect() + collect() assert capture == "A.f()" # Python version @@ -28,7 +34,7 @@ def f(self): b = B() call_f(b) del b - gc.collect() + collect() assert capture == """ PyA.PyA() PyA.f() @@ -57,7 +63,7 @@ def f(self): a2 = A2() call_f(a2) del a2 - gc.collect() + collect() assert capture == """ PyA2.PyA2() PyA2.f() @@ -70,7 +76,7 @@ def f(self): b2 = B2() call_f(b2) del b2 - gc.collect() + collect() assert capture == """ PyA2.PyA2() PyA2.f() diff --git a/tests/test_buffers.cpp b/tests/test_buffers.cpp index c3a7a9e020..057250d292 100644 --- a/tests/test_buffers.cpp +++ b/tests/test_buffers.cpp @@ -75,7 +75,7 @@ class Matrix { }; test_initializer buffers([](py::module &m) { - py::class_ mtx(m, "Matrix"); + py::class_ mtx(m, "Matrix", py::buffer_protocol()); mtx.def(py::init()) /// Construct from a buffer diff --git a/tests/test_buffers.py b/tests/test_buffers.py index f0ea964d94..85900bb50a 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -5,6 +5,32 @@ import numpy as np +@pytest.requires_numpy +def test_from_python(): + with pytest.raises(RuntimeError) as excinfo: + Matrix(np.array([1, 2, 3])) # trying to assign a 1D array + assert str(excinfo.value) == "Incompatible buffer format!" + + m3 = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.float32) + m4 = Matrix(m3) + + for i in range(m4.rows()): + for j in range(m4.cols()): + assert m3[i, j] == m4[i, j] + + cstats = ConstructorStats.get(Matrix) + assert cstats.alive() == 1 + del m3, m4 + assert cstats.alive() == 0 + assert cstats.values() == ["2x3 matrix"] + assert cstats.copy_constructions == 0 + # assert cstats.move_constructions >= 0 # Don't invoke any + assert cstats.copy_assignments == 0 + assert cstats.move_assignments == 0 + + +# PyPy: Memory leak due to the "np.array(m, copy=False)" call +@pytest.unsupported_on_pypy @pytest.requires_numpy def test_to_python(): m = Matrix(5, 5) @@ -31,27 +57,3 @@ def test_to_python(): # assert cstats.move_constructions >= 0 # Don't invoke any assert cstats.copy_assignments == 0 assert cstats.move_assignments == 0 - - -@pytest.requires_numpy -def test_from_python(): - with pytest.raises(RuntimeError) as excinfo: - Matrix(np.array([1, 2, 3])) # trying to assign a 1D array - assert str(excinfo.value) == "Incompatible buffer format!" - - m3 = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.float32) - m4 = Matrix(m3) - - for i in range(m4.rows()): - for j in range(m4.cols()): - assert m3[i, j] == m4[i, j] - - cstats = ConstructorStats.get(Matrix) - assert cstats.alive() == 1 - del m3, m4 - assert cstats.alive() == 0 - assert cstats.values() == ["2x3 matrix"] - assert cstats.copy_constructions == 0 - # assert cstats.move_constructions >= 0 # Don't invoke any - assert cstats.copy_assignments == 0 - assert cstats.move_assignments == 0 diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index 7bb52be02b..4a79182bbb 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -23,6 +23,7 @@ def test_inheritance(msg): with pytest.raises(TypeError) as excinfo: dog_bark(polly) + print(excinfo.value) assert msg(excinfo.value) == """ dog_bark(): incompatible function arguments. The following argument types are supported: 1. (arg0: m.Dog) -> str diff --git a/tests/test_keep_alive.py b/tests/test_keep_alive.py index 0cef346585..5b8d386a32 100644 --- a/tests/test_keep_alive.py +++ b/tests/test_keep_alive.py @@ -1,6 +1,11 @@ import gc +def collect(): + gc.collect() + gc.collect() + + def test_keep_alive_argument(capture): from pybind11_tests import Parent, Child @@ -9,14 +14,14 @@ def test_keep_alive_argument(capture): assert capture == "Allocating parent." with capture: p.addChild(Child()) - gc.collect() + collect() assert capture == """ Allocating child. Releasing child. """ with capture: del p - gc.collect() + collect() assert capture == "Releasing parent." with capture: @@ -24,11 +29,11 @@ def test_keep_alive_argument(capture): assert capture == "Allocating parent." with capture: p.addChildKeepAlive(Child()) - gc.collect() + collect() assert capture == "Allocating child." with capture: del p - gc.collect() + collect() assert capture == """ Releasing parent. Releasing child. @@ -43,14 +48,14 @@ def test_keep_alive_return_value(capture): assert capture == "Allocating parent." with capture: p.returnChild() - gc.collect() + collect() assert capture == """ Allocating child. Releasing child. """ with capture: del p - gc.collect() + collect() assert capture == "Releasing parent." with capture: @@ -58,11 +63,11 @@ def test_keep_alive_return_value(capture): assert capture == "Allocating parent." with capture: p.returnChildKeepAlive() - gc.collect() + collect() assert capture == "Allocating child." with capture: del p - gc.collect() + collect() assert capture == """ Releasing parent. Releasing child. @@ -77,11 +82,11 @@ def test_return_none(capture): assert capture == "Allocating parent." with capture: p.returnNullChildKeepAliveChild() - gc.collect() + collect() assert capture == "" with capture: del p - gc.collect() + collect() assert capture == "Releasing parent." with capture: @@ -89,9 +94,9 @@ def test_return_none(capture): assert capture == "Allocating parent." with capture: p.returnNullChildKeepAliveParent() - gc.collect() + collect() assert capture == "" with capture: del p - gc.collect() + collect() assert capture == "Releasing parent." diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp index 11fc900914..824e743108 100644 --- a/tests/test_methods_and_attributes.cpp +++ b/tests/test_methods_and_attributes.cpp @@ -134,10 +134,9 @@ test_initializer methods_and_attributes([](py::module &m) { .def("overloaded_const", static_cast(&ExampleMandA::overloaded)) #endif .def("__str__", &ExampleMandA::toString) - .def_readwrite("value", &ExampleMandA::value) - ; + .def_readwrite("value", &ExampleMandA::value); - py::class_(m, "TestProperties") + py::class_(m, "TestProperties", py::metaclass()) .def(py::init<>()) .def_readonly("def_readonly", &TestProperties::value) .def_readwrite("def_readwrite", &TestProperties::value) @@ -160,7 +159,7 @@ test_initializer methods_and_attributes([](py::module &m) { auto static_set2 = [](py::object, int v) { TestPropRVP::sv2.value = v; }; auto rvp_copy = py::return_value_policy::copy; - py::class_(m, "TestPropRVP") + py::class_(m, "TestPropRVP", py::metaclass()) .def(py::init<>()) .def_property_readonly("ro_ref", &TestPropRVP::get1) .def_property_readonly("ro_copy", &TestPropRVP::get2, rvp_copy) diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index 2b0f8d571c..6bc641ede3 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -90,7 +90,7 @@ def test_static_properties(): assert Type.def_property_static == 3 -@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) +@pytest.mark.parametrize("access", ["ro", "rw"]) def test_property_return_value_policies(access): from pybind11_tests import TestPropRVP @@ -116,6 +116,11 @@ def test_property_return_value_policies(access): assert getattr(obj, access + "_func").value == 1 +@pytest.mark.parametrize("access", ["static_ro", "static_rw"]) +def test_property_return_value_policies_static(access): + test_property_return_value_policies(access) + + def test_property_rvalue_policy(): """When returning an rvalue, the return value policy is automatically changed from `reference(_internal)` to `move`. The following would not work otherwise. @@ -125,10 +130,18 @@ def test_property_rvalue_policy(): instance = TestPropRVP() o = instance.rvalue assert o.value == 1 + + +def test_property_rvalue_policy_static(): + """When returning an rvalue, the return value policy is automatically changed from + `reference(_internal)` to `move`. The following would not work otherwise. + """ + from pybind11_tests import TestPropRVP o = TestPropRVP.static_rvalue assert o.value == 1 +@pytest.unsupported_on_pypy def test_dynamic_attributes(): from pybind11_tests import DynamicClass, CppDerivedDynamicClass diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py index 581cf56879..e15e252813 100644 --- a/tests/test_multiple_inheritance.py +++ b/tests/test_multiple_inheritance.py @@ -1,5 +1,7 @@ +import pytest +@pytest.unsupported_on_pypy def test_multiple_inheritance_cpp(): from pybind11_tests import MIType @@ -9,6 +11,7 @@ def test_multiple_inheritance_cpp(): assert mt.bar() == 4 +@pytest.unsupported_on_pypy def test_multiple_inheritance_mix1(): from pybind11_tests import Base2 @@ -30,6 +33,7 @@ def __init__(self, i, j): assert mt.bar() == 4 +@pytest.unsupported_on_pypy def test_multiple_inheritance_mix2(): from pybind11_tests import Base1 @@ -51,6 +55,7 @@ def __init__(self, i, j): assert mt.bar() == 4 +@pytest.unsupported_on_pypy def test_multiple_inheritance_virtbase(): from pybind11_tests import Base12a, bar_base2a, bar_base2a_sharedptr diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 1c218a10b8..8cfa095d95 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -234,11 +234,13 @@ def test_numpy_view(capture): del ac_view_1 del ac_view_2 gc.collect() + gc.collect() assert capture == """ ~ArrayClass() """ +@pytest.unsupported_on_pypy @pytest.requires_numpy def test_cast_numpy_int64_to_uint64(): from pybind11_tests.array import function_taking_uint64 diff --git a/tests/test_operator_overloading.py b/tests/test_operator_overloading.py index e0d42391ef..02ccb9633b 100644 --- a/tests/test_operator_overloading.py +++ b/tests/test_operator_overloading.py @@ -1,4 +1,3 @@ - def test_operator_overloading(): from pybind11_tests import Vector2, Vector, ConstructorStats diff --git a/tests/test_pickling.py b/tests/test_pickling.py index 5e62e1fcc7..b6f217696e 100644 --- a/tests/test_pickling.py +++ b/tests/test_pickling.py @@ -19,6 +19,7 @@ def test_roundtrip(): def test_roundtrip_with_dict(): + return False from pybind11_tests import PickleableWithDict p = PickleableWithDict("test_value") diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index ae77f82099..e1598e9ef4 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -185,7 +185,7 @@ struct MoveOutContainer { test_initializer python_types([](py::module &m) { /* No constructor is explicitly defined below. An exception is raised when trying to construct it directly from Python */ - py::class_(m, "ExamplePythonTypes", "Example 2 documentation") + py::class_(m, "ExamplePythonTypes", "Example 2 documentation", py::metaclass()) .def("get_dict", &ExamplePythonTypes::get_dict, "Return a Python dictionary") .def("get_dict_2", &ExamplePythonTypes::get_dict_2, "Return a C++ dictionary") .def("get_list", &ExamplePythonTypes::get_list, "Return a Python list") @@ -212,8 +212,7 @@ test_initializer python_types([](py::module &m) { .def("test_print", &ExamplePythonTypes::test_print, "test the print function") .def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance") .def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member") - .def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)") - ; + .def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)"); m.def("test_print_function", []() { py::print("Hello, World!"); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 9fe1ef71e5..531c3286fe 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -132,8 +132,13 @@ def __repr__(self): assert cstats.alive() == 0 -def test_docs(doc): +# PyPy does not seem to propagate the tp_docs field at the moment +@pytest.unsupported_on_pypy +def test_class_docs(doc): assert doc(ExamplePythonTypes) == "Example 2 documentation" + + +def test_method_docs(doc): assert doc(ExamplePythonTypes.get_dict) == """ get_dict(self: m.ExamplePythonTypes) -> dict diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index a9aecd67f7..f95616ffbe 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -206,6 +206,9 @@ def lucky_number(self): assert obj.say_everything() == "BT -7" +# PyPy: Reference counts cause call with noncopyable instance +# to fail in ncv1.print_nc() +@pytest.unsupported_on_pypy @pytest.mark.skipif(not hasattr(pybind11_tests, 'NCVirt'), reason="NCVirt test broken on ICPC") def test_move_support(): From 5c5de7ffeba3400c626cd3da1e7ffaa3bda1ea66 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Sat, 3 Dec 2016 02:26:16 +0100 Subject: [PATCH 06/14] fixes for various regressions --- include/pybind11/pybind11.h | 16 ++++++++-------- tests/constructor_stats.h | 2 ++ tests/test_inheritance.py | 1 - 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index d170b2d39f..46fe0b72b9 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -686,8 +686,8 @@ class generic_type : public object { /* Create a custom metaclass if requested (used for static properties) */ object metaclass; if (rec->metaclass) { - std::string name_ = full_name + "__Meta"; - object name = reinterpret_steal(PYBIND11_FROM_STRING(name_.c_str())); + std::string meta_name_ = full_name + "__Meta"; + object meta_name = reinterpret_steal(PYBIND11_FROM_STRING(meta_name_.c_str())); metaclass = reinterpret_steal(PyType_Type.tp_alloc(&PyType_Type, 0)); if (!metaclass || !name) pybind11_fail("generic_type::generic_type(): unable to create metaclass!"); @@ -698,14 +698,13 @@ class generic_type : public object { turn find the newly constructed type in an invalid state) */ auto type = (PyHeapTypeObject*) metaclass.ptr(); - type->ht_name = name.release().ptr(); - + type->ht_name = meta_name.release().ptr(); #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 /* Qualified names for Python >= 3.3 */ type->ht_qualname = ht_qualname.release().ptr(); #endif - type->ht_type.tp_name = strdup(name_.c_str()); + type->ht_type.tp_name = strdup(meta_name_.c_str()); type->ht_type.tp_base = &PyType_Type; type->ht_type.tp_flags |= (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE) & ~Py_TPFLAGS_HAVE_GC; @@ -878,9 +877,8 @@ class generic_type : public object { PyObject_ClearWeakRefs((PyObject *) self); PyObject **dict_ptr = _PyObject_GetDictPtr((PyObject *) self); - if (dict_ptr) { + if (dict_ptr) Py_CLEAR(*dict_ptr); - } } Py_TYPE(self)->tp_free((PyObject*) self); } @@ -902,12 +900,14 @@ class generic_type : public object { void *get_buffer_data) { PyHeapTypeObject *type = (PyHeapTypeObject*) m_ptr; auto tinfo = detail::get_type_info(&type->ht_type); - if ((type->ht_type.tp_flags & Py_TPFLAGS_HAVE_NEWBUFFER) == 0) + + if (!type->ht_type.tp_as_buffer) pybind11_fail( "To be able to register buffer protocol support for the type '" + std::string(tinfo->type->tp_name) + "' the associated class<>(..) invocation must " "include the pybind11::buffer_protocol() annotation!"); + tinfo->get_buffer = get_buffer; tinfo->get_buffer_data = get_buffer_data; } diff --git a/tests/constructor_stats.h b/tests/constructor_stats.h index 9ef3248408..de5c133c12 100644 --- a/tests/constructor_stats.h +++ b/tests/constructor_stats.h @@ -158,7 +158,9 @@ class ConstructorStats { // Gets constructor stats from a C++ type template static ConstructorStats& get() { +#if defined(PYPY_VERSION) gc(); +#endif return get(typeid(T)); } diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index 4a79182bbb..7bb52be02b 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -23,7 +23,6 @@ def test_inheritance(msg): with pytest.raises(TypeError) as excinfo: dog_bark(polly) - print(excinfo.value) assert msg(excinfo.value) == """ dog_bark(): incompatible function arguments. The following argument types are supported: 1. (arg0: m.Dog) -> str From 9f266c9c293e2ffeb611ecd3db767f1f9e21c7ba Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Sat, 3 Dec 2016 23:32:09 +0100 Subject: [PATCH 07/14] moved heavy templated code into non-templated base class (-1% object code reduction) --- include/pybind11/pybind11.h | 46 +++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 46fe0b72b9..2364d14c87 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -942,6 +942,31 @@ class generic_type : public object { } 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) { + pybind11::str doc_obj = pybind11::str( + (rec_fget->doc && pybind11::options::show_user_defined_docstrings()) + ? rec_fget->doc : ""); + const auto property = reinterpret_steal( + PyObject_CallFunctionObjArgs((PyObject *) &PyProperty_Type, fget.ptr() ? fget.ptr() : Py_None, + fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr(), nullptr)); + if (rec_fget->is_method && rec_fget->scope) { + attr(name) = property; + } else { + auto mclass = handle((PyObject *) PYBIND11_OB_TYPE(*((PyTypeObject *) m_ptr))); + + if ((PyTypeObject *) mclass.ptr() == &PyType_Type) + pybind11_fail( + "Adding static properties to the type '" + + std::string(((PyTypeObject *) m_ptr)->tp_name) + + "' requires the type to have a custom metaclass. Please " + "ensure that one is created by supplying the pybind11::metaclass() " + "annotation to the associated class_<>(..) invocation."); + mclass.attr(name) = property; + } + } }; NAMESPACE_END(detail) @@ -1145,26 +1170,7 @@ class class_ : public detail::generic_type { rec_fset->doc = strdup(rec_fset->doc); } } - pybind11::str doc_obj = pybind11::str( - (rec_fget->doc && pybind11::options::show_user_defined_docstrings()) - ? rec_fget->doc : ""); - const auto property = reinterpret_steal( - PyObject_CallFunctionObjArgs((PyObject *) &PyProperty_Type, fget.ptr() ? fget.ptr() : Py_None, - fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr(), nullptr)); - if (rec_fget->is_method && rec_fget->scope) { - attr(name) = property; - } else { - auto mclass = handle((PyObject *) PYBIND11_OB_TYPE(*((PyTypeObject *) m_ptr))); - - if ((PyTypeObject *) mclass.ptr() == &PyType_Type) - pybind11_fail( - "Adding static properties to the type '" + - std::string(((PyTypeObject *) m_ptr)->tp_name) + - "' requires the type to have a custom metaclass. Please " - "ensure that one is created by supplying the pybind11::metaclass() " - "annotation to the associated class_<>(..) invocation."); - mclass.attr(name) = property; - } + def_property_static_impl(name, fget, fset, rec_fget); return *this; } From 5b68201bfe4b4d3f508c8140de33b08cb3861083 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 6 Dec 2016 00:06:20 +0100 Subject: [PATCH 08/14] enable travis-CI for the PyPy version --- .travis.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b9c5ab540d..d4bdafd7e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,6 +29,13 @@ matrix: - os: osx osx_image: xcode7.3 env: PYTHON=3.5 CPP=14 CLANG + # Test a PyPy 2.7 nightly build + - os: linux + env: PYPY=1 PYTHON=2.7 CPP=11 GCC=4.8 + addons: + apt: + sources: [ubuntu-toolchain-r-test, kubuntu-backports] + packages: [g++-4.8, cmake] # A barebones build makes sure everything still works without optional deps (numpy/scipy/eigen) # and also tests the automatic discovery functions in CMake (Python version, C++ standard). - os: linux @@ -70,9 +77,18 @@ before_install: fi if [ -n "$CPP" ]; then export CPP=-std=c++$CPP; fi if [ "${PYTHON:0:1}" = "3" ]; then export PY=3; fi + if [ -n "$PYPY" ]; then + curl http://buildbot.pypy.org/nightly/trunk/pypy-c-jit-latest-linux64.tar.bz2 | tar -xj + export PYPY_BINARY=$(echo `pwd`/pypy-c-jit*/bin/pypy) + export CMAKE_EXTRA_ARGS="-DPYTHON_EXECUTABLE:FILEPATH=$PYPY_BINARY" + fi if [ -n "$DEBUG" ]; then export CMAKE_EXTRA_ARGS="-DCMAKE_BUILD_TYPE=Debug"; fi - | - # Initialize enviornment + # Initialize environment + if [ -n "$PYPY" ]; then + $PYPY_BINARY -m ensurepip + $PYPY_BINARY -m pip install pytest + fi if [ -n "$DOCKER" ]; then docker pull $DOCKER export containerid=$(docker run --detach --tty \ From 7e34af7e048c1d5a0d4e11ee99cc0da32bddcbdf Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 9 Dec 2016 13:55:12 +0100 Subject: [PATCH 09/14] enable previously disabled testcases on pypy (MI, docstrings) --- tests/test_multiple_inheritance.cpp | 4 ---- tests/test_multiple_inheritance.py | 7 ------- tests/test_python_types.py | 1 - 3 files changed, 12 deletions(-) diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index 5b184a872a..c57cb852a9 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -10,8 +10,6 @@ #include "pybind11_tests.h" -#if !defined(PYPY_VERSION) - struct Base1 { Base1(int i) : i(i) { } int foo() { return i; } @@ -83,5 +81,3 @@ 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 b) { return b->bar(); }); }); - -#endif diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py index e15e252813..c10298d702 100644 --- a/tests/test_multiple_inheritance.py +++ b/tests/test_multiple_inheritance.py @@ -1,7 +1,3 @@ -import pytest - - -@pytest.unsupported_on_pypy def test_multiple_inheritance_cpp(): from pybind11_tests import MIType @@ -11,7 +7,6 @@ def test_multiple_inheritance_cpp(): assert mt.bar() == 4 -@pytest.unsupported_on_pypy def test_multiple_inheritance_mix1(): from pybind11_tests import Base2 @@ -33,7 +28,6 @@ def __init__(self, i, j): assert mt.bar() == 4 -@pytest.unsupported_on_pypy def test_multiple_inheritance_mix2(): from pybind11_tests import Base1 @@ -55,7 +49,6 @@ def __init__(self, i, j): assert mt.bar() == 4 -@pytest.unsupported_on_pypy def test_multiple_inheritance_virtbase(): from pybind11_tests import Base12a, bar_base2a, bar_base2a_sharedptr diff --git a/tests/test_python_types.py b/tests/test_python_types.py index 531c3286fe..347abaaee0 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -133,7 +133,6 @@ def __repr__(self): # PyPy does not seem to propagate the tp_docs field at the moment -@pytest.unsupported_on_pypy def test_class_docs(doc): assert doc(ExamplePythonTypes) == "Example 2 documentation" From 689946f0e75f4f23e05d29425e5b5d615898d04e Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Tue, 13 Dec 2016 21:25:30 +0100 Subject: [PATCH 10/14] update docs to describe new class_ tags --- docs/advanced/classes.rst | 21 +++++++++++++++------ docs/advanced/pycpp/numpy.rst | 13 ++++++++----- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index 4a423b578b..e20895e6d7 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -422,15 +422,24 @@ The section on :ref:`properties` discussed the creation of instance properties that are implemented in terms of C++ getters and setters. Static properties can also be created in a similar way to expose getters and -setters of static class attributes. It is important to note that the implicit -``self`` argument also exists in this case and is used to pass the Python -``type`` subclass instance. This parameter will often not be needed by the C++ -side, and the following example illustrates how to instantiate a lambda getter -function that ignores it: +setters of static class attributes. Two things are important to note: + +1. Static properties are implemented by instrumenting the *metaclass* of the + class in question -- however, this requires the class to have a modifiable + metaclass in the first place. pybind11 provides a ``py::metaclass()`` + annotation that must be specified in the ``class_`` constructor, or any + later method calls to ``def_{property_,∅}_{readwrite,readonly}_static`` will + fail (see the example below). + +2. For static properties defined in terms of setter and getter functions, note + that the implicit ``self`` argument also exists in this case and is used to + pass the Python ``type`` subclass instance. This parameter will often not be + needed by the C++ side, and the following example illustrates how to + instantiate a lambda getter function that ignores it: .. code-block:: cpp - py::class_(m, "Foo") + py::class_(m, "Foo", py::metaclass()) .def_property_readonly_static("foo", [](py::object /* self */) { return Foo(); }); Operator overloading diff --git a/docs/advanced/pycpp/numpy.rst b/docs/advanced/pycpp/numpy.rst index 8b46b7c834..111ff0e3cf 100644 --- a/docs/advanced/pycpp/numpy.rst +++ b/docs/advanced/pycpp/numpy.rst @@ -33,7 +33,7 @@ completely avoid copy operations with Python expressions like .. code-block:: cpp - py::class_(m, "Matrix") + py::class_(m, "Matrix", py::buffer_protocol()) .def_buffer([](Matrix &m) -> py::buffer_info { return py::buffer_info( m.data(), /* Pointer to buffer */ @@ -46,9 +46,12 @@ completely avoid copy operations with Python expressions like ); }); -The snippet above binds a lambda function, which can create ``py::buffer_info`` -description records on demand describing a given matrix. The contents of -``py::buffer_info`` mirror the Python buffer protocol specification. +Supporting the buffer protocol in a new type involves specifying the special +``py::buffer_protocol()`` tag in the ``py::class_`` constructor and calling the +``def_buffer()`` method with a lambda function that creates a +``py::buffer_info`` description record on demand describing a given matrix +instance. The contents of ``py::buffer_info`` mirror the Python buffer protocol +specification. .. code-block:: cpp @@ -77,7 +80,7 @@ buffer objects (e.g. a NumPy matrix). typedef Matrix::Scalar Scalar; constexpr bool rowMajor = Matrix::Flags & Eigen::RowMajorBit; - py::class_(m, "Matrix") + py::class_(m, "Matrix", py::buffer_protocol()) .def("__init__", [](Matrix &m, py::buffer b) { typedef Eigen::Stride Strides; From 0d83fcfca5cb525bb7d97760380a049c88ee5fa3 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Thu, 15 Dec 2016 10:48:33 +0100 Subject: [PATCH 11/14] integrated feedback by @dean0x7d --- include/pybind11/pybind11.h | 3 +-- tests/conftest.py | 11 +++++++++- tests/test_alias_initialization.py | 15 +++++--------- tests/test_buffers.py | 5 ++++- tests/test_issues.py | 9 ++++---- tests/test_keep_alive.py | 31 ++++++++++++---------------- tests/test_methods_and_attributes.py | 8 ++----- tests/test_numpy_array.py | 7 +++---- tests/test_pickling.py | 4 +++- tests/test_virtual_functions.py | 2 +- 10 files changed, 46 insertions(+), 49 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 2364d14c87..bc1f37003e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -607,9 +607,8 @@ class module : public object { NAMESPACE_BEGIN(detail) extern "C" inline PyObject *get_dict(PyObject *op, void *) { PyObject *&dict = *_PyObject_GetDictPtr(op); - if (!dict) { + if (!dict) dict = PyDict_New(); - } Py_XINCREF(dict); return dict; } diff --git a/tests/conftest.py b/tests/conftest.py index 6237bf499d..91aad6bcb1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ import sys import contextlib import platform +import gc _unicode_marker = re.compile(r'u(\'[^\']*\')') _long_marker = re.compile(r'([0-9])L') @@ -177,6 +178,13 @@ def suppress(exception): pass +def gc_collect(): + ''' Run the garbage collector twice (needed when running + reference counting tests with PyPy) ''' + gc.collect() + gc.collect() + + def pytest_namespace(): """Add import suppression and test requirements to `pytest` namespace""" try: @@ -202,7 +210,8 @@ def pytest_namespace(): reason="eigen and/or numpy are not installed"), 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, reason="eigen and/or scipy are not installed"), - 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy") + 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"), + 'gc_collect' : gc_collect } diff --git a/tests/test_alias_initialization.py b/tests/test_alias_initialization.py index 0f3463ace7..fb90cfc7ba 100644 --- a/tests/test_alias_initialization.py +++ b/tests/test_alias_initialization.py @@ -1,9 +1,4 @@ -import gc - - -def collect(): - gc.collect() - gc.collect() +import pytest def test_alias_delay_initialization1(capture): @@ -26,7 +21,7 @@ def f(self): a = A() call_f(a) del a - collect() + pytest.gc_collect() assert capture == "A.f()" # Python version @@ -34,7 +29,7 @@ def f(self): b = B() call_f(b) del b - collect() + pytest.gc_collect() assert capture == """ PyA.PyA() PyA.f() @@ -63,7 +58,7 @@ def f(self): a2 = A2() call_f(a2) del a2 - collect() + pytest.gc_collect() assert capture == """ PyA2.PyA2() PyA2.f() @@ -76,7 +71,7 @@ def f(self): b2 = B2() call_f(b2) del b2 - collect() + pytest.gc_collect() assert capture == """ PyA2.PyA2() PyA2.f() diff --git a/tests/test_buffers.py b/tests/test_buffers.py index 85900bb50a..956839c1ca 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -29,7 +29,8 @@ def test_from_python(): assert cstats.move_assignments == 0 -# PyPy: Memory leak due to the "np.array(m, copy=False)" call +# PyPy: Memory leak in the "np.array(m, copy=False)" call +# https://bitbucket.org/pypy/pypy/issues/2444 @pytest.unsupported_on_pypy @pytest.requires_numpy def test_to_python(): @@ -49,8 +50,10 @@ def test_to_python(): cstats = ConstructorStats.get(Matrix) assert cstats.alive() == 1 del m + pytest.gc_collect() assert cstats.alive() == 1 del m2 # holds an m reference + pytest.gc_collect() assert cstats.alive() == 0 assert cstats.values() == ["5x5 matrix"] assert cstats.copy_constructions == 0 diff --git a/tests/test_issues.py b/tests/test_issues.py index 2098ff8a38..e60b5ca907 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -1,5 +1,4 @@ import pytest -import gc from pybind11_tests import ConstructorStats @@ -55,7 +54,7 @@ def test_shared_ptr_gc(): el = ElementList() for i in range(10): el.add(ElementA(i)) - gc.collect() + pytest.gc_collect() for i, v in enumerate(el.get()): assert i == v.value() @@ -130,13 +129,13 @@ def test_nested(): assert c.b.a.as_base().value == 42 del c - gc.collect() + pytest.gc_collect() del a # Should't delete while abase is still alive - gc.collect() + pytest.gc_collect() assert abase.value == 42 del abase, b - gc.collect() + pytest.gc_collect() def test_move_fallback(): diff --git a/tests/test_keep_alive.py b/tests/test_keep_alive.py index 5b8d386a32..bfd7d40c3e 100644 --- a/tests/test_keep_alive.py +++ b/tests/test_keep_alive.py @@ -1,9 +1,4 @@ -import gc - - -def collect(): - gc.collect() - gc.collect() +import pytest def test_keep_alive_argument(capture): @@ -14,14 +9,14 @@ def test_keep_alive_argument(capture): assert capture == "Allocating parent." with capture: p.addChild(Child()) - collect() + pytest.gc_collect() assert capture == """ Allocating child. Releasing child. """ with capture: del p - collect() + pytest.gc_collect() assert capture == "Releasing parent." with capture: @@ -29,11 +24,11 @@ def test_keep_alive_argument(capture): assert capture == "Allocating parent." with capture: p.addChildKeepAlive(Child()) - collect() + pytest.gc_collect() assert capture == "Allocating child." with capture: del p - collect() + pytest.gc_collect() assert capture == """ Releasing parent. Releasing child. @@ -48,14 +43,14 @@ def test_keep_alive_return_value(capture): assert capture == "Allocating parent." with capture: p.returnChild() - collect() + pytest.gc_collect() assert capture == """ Allocating child. Releasing child. """ with capture: del p - collect() + pytest.gc_collect() assert capture == "Releasing parent." with capture: @@ -63,11 +58,11 @@ def test_keep_alive_return_value(capture): assert capture == "Allocating parent." with capture: p.returnChildKeepAlive() - collect() + pytest.gc_collect() assert capture == "Allocating child." with capture: del p - collect() + pytest.gc_collect() assert capture == """ Releasing parent. Releasing child. @@ -82,11 +77,11 @@ def test_return_none(capture): assert capture == "Allocating parent." with capture: p.returnNullChildKeepAliveChild() - collect() + pytest.gc_collect() assert capture == "" with capture: del p - collect() + pytest.gc_collect() assert capture == "Releasing parent." with capture: @@ -94,9 +89,9 @@ def test_return_none(capture): assert capture == "Allocating parent." with capture: p.returnNullChildKeepAliveParent() - collect() + pytest.gc_collect() assert capture == "" with capture: del p - collect() + pytest.gc_collect() assert capture == "Releasing parent." diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index 6bc641ede3..1502f77f5f 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -90,7 +90,7 @@ def test_static_properties(): assert Type.def_property_static == 3 -@pytest.mark.parametrize("access", ["ro", "rw"]) +@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) def test_property_return_value_policies(access): from pybind11_tests import TestPropRVP @@ -116,11 +116,6 @@ def test_property_return_value_policies(access): assert getattr(obj, access + "_func").value == 1 -@pytest.mark.parametrize("access", ["static_ro", "static_rw"]) -def test_property_return_value_policies_static(access): - test_property_return_value_policies(access) - - def test_property_rvalue_policy(): """When returning an rvalue, the return value policy is automatically changed from `reference(_internal)` to `move`. The following would not work otherwise. @@ -141,6 +136,7 @@ def test_property_rvalue_policy_static(): assert o.value == 1 +# https://bitbucket.org/pypy/pypy/issues/2447 @pytest.unsupported_on_pypy def test_dynamic_attributes(): from pybind11_tests import DynamicClass, CppDerivedDynamicClass diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 8cfa095d95..b96790c390 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -1,5 +1,4 @@ import pytest -import gc with pytest.suppress(ImportError): import numpy as np @@ -220,7 +219,7 @@ def test_numpy_view(capture): ac_view_2 = ac.numpy_view() assert np.all(ac_view_1 == np.array([1, 2], dtype=np.int32)) del ac - gc.collect() + pytest.gc_collect() assert capture == """ ArrayClass() ArrayClass::numpy_view() @@ -233,8 +232,8 @@ def test_numpy_view(capture): with capture: del ac_view_1 del ac_view_2 - gc.collect() - gc.collect() + pytest.gc_collect() + pytest.gc_collect() assert capture == """ ~ArrayClass() """ diff --git a/tests/test_pickling.py b/tests/test_pickling.py index b6f217696e..548c618af2 100644 --- a/tests/test_pickling.py +++ b/tests/test_pickling.py @@ -1,3 +1,5 @@ +import pytest + try: import cPickle as pickle # Use cPickle on Python 2.7 except ImportError: @@ -18,8 +20,8 @@ def test_roundtrip(): assert p2.extra2() == p.extra2() +@pytest.unsupported_on_pypy def test_roundtrip_with_dict(): - return False from pybind11_tests import PickleableWithDict p = PickleableWithDict("test_value") diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index f95616ffbe..b11c699dfa 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -206,7 +206,7 @@ def lucky_number(self): assert obj.say_everything() == "BT -7" -# PyPy: Reference counts cause call with noncopyable instance +# PyPy: Reference count > 1 causes call with noncopyable instance # to fail in ncv1.print_nc() @pytest.unsupported_on_pypy @pytest.mark.skipif(not hasattr(pybind11_tests, 'NCVirt'), From 4de034598bac26ffa6a43ec583313f071ac3afee Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Thu, 15 Dec 2016 11:05:02 +0100 Subject: [PATCH 12/14] fix flake8 warning --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 91aad6bcb1..b69fd6cb28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -211,7 +211,7 @@ def pytest_namespace(): 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, reason="eigen and/or scipy are not installed"), 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"), - 'gc_collect' : gc_collect + 'gc_collect': gc_collect } From 68a6f42c1aaa1c3263fc26bf37290a25e6415f12 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 16 Dec 2016 14:42:38 +0100 Subject: [PATCH 13/14] removed a leftover piece of code from a prior commit --- include/pybind11/pytypes.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index eaefd13d4a..a89aad78d9 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -700,10 +700,6 @@ class float_ : public object { operator double() const { return (double) PyFloat_AsDouble(m_ptr); } }; -#if defined(PYPY_VERSION) -inline bool PyWeakref_Check(PyObject *obj); -#endif - class weakref : public object { public: PYBIND11_OBJECT_DEFAULT(weakref, object, PyWeakref_Check) From 2f418d2b87c26334f2b1799535baf5be09cb686d Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 16 Dec 2016 14:55:38 +0100 Subject: [PATCH 14/14] mention pypy support in docs --- README.md | 11 +++++++---- docs/intro.rst | 3 +++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cfe3f1519c..96feb52fff 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ become an excessively large and unnecessary dependency. Think of this library as a tiny self-contained version of Boost.Python with everything stripped away that isn't relevant for binding generation. Without comments, the core header files only require ~2.5K lines of code and depend on -Python (2.7 or 3.x) and the C++ standard library. This compact implementation +Python (2.7 or 3.x, or PyPy2.7 >= 5.5) and the C++ standard library. This compact implementation was possible thanks to some of the new C++11 language features (specifically: tuples, lambda functions and variadic templates). Since its creation, this library has grown beyond Boost.Python in many ways, leading to dramatically @@ -58,12 +58,15 @@ pybind11 can map the following core C++ features to Python ## Goodies In addition to the core functionality, pybind11 provides some extra goodies: -- pybind11 uses C++11 move constructors and move assignment operators whenever - possible to efficiently transfer custom data types. +- Python 2.7, 3.x, and PyPy (PyPy2.7 >= 5.5) are supported with an + implementation-agnostic interface. - It is possible to bind C++11 lambda functions with captured variables. The lambda capture data is stored inside the resulting Python function object. +- pybind11 uses C++11 move constructors and move assignment operators whenever + possible to efficiently transfer custom data types. + - It's easy to expose the internal storage of custom data types through Pythons' buffer protocols. This is handy e.g. for fast conversion between C++ matrix classes like Eigen and NumPy without expensive copy operations. @@ -100,7 +103,7 @@ In addition to the core functionality, pybind11 provides some extra goodies: ## About -This project was created by [Wenzel Jakob](https://www.mitsuba-renderer.org/~wenzel/). +This project was created by [Wenzel Jakob](http://rgl.epfl.ch/people/wjakob). Significant features and/or improvements to the code were contributed by Jonas Adler, Sylvain Corlay, diff --git a/docs/intro.rst b/docs/intro.rst index 429a01cdd2..f22eeedb9c 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -51,6 +51,9 @@ Goodies ******* In addition to the core functionality, pybind11 provides some extra goodies: +- Python 2.7, 3.x, and PyPy (PyPy2.7 >= 5.5) are supported with an + implementation-agnostic interface. + - It is possible to bind C++11 lambda functions with captured variables. The lambda capture data is stored inside the resulting Python function object.