From e18bc02fc9bc17f537aa3d57ebd08c277b6a3959 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Tue, 25 Oct 2016 22:12:39 +0200 Subject: [PATCH] Add default and converting constructors for all concrete Python types * Deprecate the `py::object::str()` member function since `py::str(obj)` is now equivalent and preferred * Make `py::repr()` a free function * Make sure obj.cast() works as expected when T is a Python type `obj.cast()` should be the same as `T(obj)`, i.e. it should convert the given object to a different Python type. However, `obj.cast()` usually calls `type_caster::load()` which only checks the type without doing any actual conversion. That causes a very unexpected `cast_error`. This commit makes it so that `obj.cast()` and `T(obj)` are the same when T is a Python type. * Simplify pytypes converting constructor implementation It's not necessary to maintain a full set of converting constructors and assignment operators + const& and &&. A single converting const& constructor will work and there is no impact on binary size. On the other hand, the conversion functions can be significantly simplified. --- docs/changelog.rst | 2 +- include/pybind11/attr.h | 4 +- include/pybind11/cast.h | 22 +++--- include/pybind11/common.h | 2 - include/pybind11/numpy.h | 8 +-- include/pybind11/pybind11.h | 12 ++-- include/pybind11/pytypes.h | 138 ++++++++++++++++++++---------------- include/pybind11/stl.h | 2 +- tests/test_numpy_dtypes.cpp | 16 ++--- tests/test_python_types.cpp | 46 +++++++++++- tests/test_python_types.py | 26 +++++++ 11 files changed, 179 insertions(+), 99 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 87d38d3d8..15cf8d959 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -28,7 +28,7 @@ Breaking changes queued for v2.0.0 (Not yet released) (now uses prefix increment operator); it now also accepts iterators with different begin/end types as long as they are equality comparable. * ``arg()`` now accepts a wider range of argument types for default values -* Added ``repr()`` method to the ``handle`` class. +* Added ``py::repr()`` function which is equivalent to Python's builtin ``repr()``. * Added support for registering structured dtypes via ``PYBIND11_NUMPY_DTYPE()`` macro. * Added ``PYBIND11_STR_TYPE`` macro which maps to the ``builtins.str`` type. * Added a simplified ``buffer_info`` constructor for 1-dimensional buffers. diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 1ea925c18..d728210e0 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -265,9 +265,9 @@ template <> struct process_attribute : process_attribute_default { auto descr = "'" + std::string(a.name) + ": " + a.type + "'"; if (r->class_) { if (r->name) - descr += " in method '" + (std::string) r->class_.str() + "." + (std::string) r->name + "'"; + descr += " in method '" + (std::string) str(r->class_) + "." + (std::string) r->name + "'"; else - descr += " in method of '" + (std::string) r->class_.str() + "'"; + descr += " in method of '" + (std::string) str(r->class_) + "'"; } else if (r->name) { descr += " in function named '" + (std::string) r->name + "'"; } diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 90d9d4b33..fa9d21d7c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -132,7 +132,7 @@ PYBIND11_NOINLINE inline std::string error_string() { errorString += ": "; } if (scope.value) - errorString += (std::string) handle(scope.value).str(); + errorString += (std::string) str(scope.value); PyErr_NormalizeException(&scope.type, &scope.value, &scope.trace); @@ -1056,7 +1056,7 @@ template type_caster &load_type(type_ca throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)"); #else throw cast_error("Unable to cast Python instance of type " + - (std::string) handle.get_type().str() + " to C++ type '" + type_id() + "''"); + (std::string) str(handle.get_type()) + " to C++ type '" + type_id() + "''"); #endif } return conv; @@ -1070,16 +1070,20 @@ template make_caster load_type(const handle &handle) { NAMESPACE_END(detail) -template T cast(const handle &handle) { +template ::value, int> = 0> +T cast(const handle &handle) { static_assert(!detail::cast_is_temporary_value_reference::value, "Unable to cast type to reference: value is local to type caster"); using type_caster = detail::make_caster; return detail::load_type(handle).operator typename type_caster::template cast_op_type(); } -template object cast(const T &value, - return_value_policy policy = return_value_policy::automatic_reference, - handle parent = handle()) { +template ::value, int> = 0> +T cast(const handle &handle) { return {handle, true}; } + +template ::value, int> = 0> +object cast(const T &value, return_value_policy policy = return_value_policy::automatic_reference, + handle parent = handle()) { if (policy == return_value_policy::automatic) policy = std::is_pointer::value ? return_value_policy::take_ownership : return_value_policy::copy; else if (policy == return_value_policy::automatic_reference) @@ -1097,7 +1101,7 @@ detail::enable_if_t::value || detail::move_if_unreference throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references" " (compile in debug mode for details)"); #else - throw cast_error("Unable to move from Python " + (std::string) obj.get_type().str() + + throw cast_error("Unable to move from Python " + (std::string) str(obj.get_type()) + " instance to C++ " + type_id() + " instance: instance has multiple references"); #endif @@ -1274,7 +1278,7 @@ public: int _[] = { 0, (process(args_list, std::forward(values)), 0)... }; ignore_unused(_); - m_args = object(PyList_AsTuple(args_list.ptr()), false); + m_args = std::move(args_list); } const tuple &args() const & { return m_args; } @@ -1336,7 +1340,7 @@ private: #if defined(NDEBUG) multiple_values_error(); #else - multiple_values_error(k.first.str()); + multiple_values_error(str(k.first)); #endif } m_kwargs[k.first] = k.second; diff --git a/include/pybind11/common.h b/include/pybind11/common.h index f6e54e7f2..e0ebedeaf 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -111,7 +111,6 @@ #define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyBytes_FromStringAndSize #define PYBIND11_BYTES_AS_STRING_AND_SIZE PyBytes_AsStringAndSize #define PYBIND11_BYTES_AS_STRING PyBytes_AsString -#define PYBIND11_BYTES_CHECK PyBytes_Check #define PYBIND11_LONG_CHECK(o) PyLong_Check(o) #define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o) #define PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o) PyLong_AsUnsignedLongLong(o) @@ -130,7 +129,6 @@ #define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyString_FromStringAndSize #define PYBIND11_BYTES_AS_STRING_AND_SIZE PyString_AsStringAndSize #define PYBIND11_BYTES_AS_STRING PyString_AsString -#define PYBIND11_BYTES_CHECK PyString_Check #define PYBIND11_LONG_CHECK(o) (PyInt_Check(o) || PyLong_Check(o)) #define PYBIND11_LONG_AS_LONGLONG(o) (PyInt_Check(o) ? (long long) PyLong_AsLong(o) : PyLong_AsLongLong(o)) #define PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o) (PyInt_Check(o) ? (unsigned long long) PyLong_AsUnsignedLong(o) : PyLong_AsUnsignedLongLong(o)) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 2ffbc5f5c..9fe438502 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -540,10 +540,12 @@ protected: template class array_t : public array { public: - PYBIND11_OBJECT_CVT(array_t, array, is_non_null, m_ptr = ensure_(m_ptr)); - array_t() : array() { } + array_t(handle h, bool borrowed) : array(h, borrowed) { m_ptr = ensure_(m_ptr); } + + array_t(const object &o) : array(o) { m_ptr = ensure_(m_ptr); } + explicit array_t(const buffer_info& info) : array(info) { } array_t(const std::vector &shape, @@ -588,8 +590,6 @@ public: return *(static_cast(array::mutable_data()) + byte_offset(size_t(index)...) / itemsize()); } - static bool is_non_null(PyObject *ptr) { return ptr != nullptr; } - static PyObject *ensure_(PyObject *ptr) { if (ptr == nullptr) return nullptr; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 0cd35c9ba..ca116eb4c 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -504,7 +504,7 @@ protected: msg += "\nInvoked with: "; tuple args_(args, true); for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) { - msg += static_cast(static_cast(args_[ti]).str()); + msg += static_cast(pybind11::str(args_[ti])); if ((ti + 1) != args_.size() ) msg += ", "; } @@ -665,11 +665,9 @@ protected: #endif size_t num_bases = rec->bases.size(); - tuple bases(num_bases); - for (size_t i = 0; i < num_bases; ++i) - bases[i] = rec->bases[i]; + auto bases = tuple(rec->bases); - std::string full_name = (scope_module ? ((std::string) scope_module.str() + "." + rec->name) + std::string full_name = (scope_module ? ((std::string) pybind11::str(scope_module) + "." + rec->name) : std::string(rec->name)); char *tp_doc = nullptr; @@ -1470,7 +1468,7 @@ NAMESPACE_BEGIN(detail) PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) { auto strings = tuple(args.size()); for (size_t i = 0; i < args.size(); ++i) { - strings[i] = args[i].str(); + strings[i] = str(args[i]); } auto sep = kwargs.contains("sep") ? kwargs["sep"] : cast(" "); auto line = sep.attr("join")(strings); @@ -1654,7 +1652,7 @@ inline function get_type_overload(const void *this_ptr, const detail::type_info /* Don't call dispatch code if invoked from overridden function */ PyFrameObject *frame = PyThreadState_Get()->frame; - if (frame && (std::string) pybind11::handle(frame->f_code->co_name).str() == name && + if (frame && (std::string) str(frame->f_code->co_name) == name && frame->f_code->co_argcount > 0) { PyFrame_FastToLocals(frame); PyObject *self_caller = PyDict_GetItem( diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index ced8eb076..9257643e8 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -68,8 +68,8 @@ public: object call(Args&&... args) const; bool is_none() const { return derived().ptr() == Py_None; } + PYBIND11_DEPRECATED("Instead of obj.str(), use py::str(obj)") pybind11::str str() const; - pybind11::str repr() const; int ref_count() const { return static_cast(Py_REFCNT(derived().ptr())); } handle get_type() const; @@ -222,13 +222,13 @@ public: template PYBIND11_DEPRECATED("Use of obj.attr(...) as bool is deprecated in favor of pybind11::hasattr(obj, ...)") - operator enable_if_t::value || + explicit operator enable_if_t::value || std::is_same::value, bool>() const { return hasattr(obj, key); } template PYBIND11_DEPRECATED("Use of obj[key] as bool is deprecated in favor of obj.contains(key)") - operator enable_if_t::value, bool>() const { + explicit operator enable_if_t::value, bool>() const { return obj.contains(key); } @@ -390,20 +390,23 @@ class unpacking_collector; NAMESPACE_END(detail) -#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, CvtStmt) \ +#define PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ public: \ - Name(const handle &h, bool borrowed) : Parent(h, borrowed) { CvtStmt; } \ - /* These are deliberately not 'explicit' to allow implicit conversion from object: */ \ - Name(const object& o): Parent(o) { CvtStmt; } \ - Name(object&& o) noexcept : Parent(std::move(o)) { CvtStmt; } \ - Name& operator=(object&& o) noexcept { (void) object::operator=(std::move(o)); CvtStmt; return *this; } \ - Name& operator=(const object& o) { return static_cast(object::operator=(o)); CvtStmt; } \ PYBIND11_DEPRECATED("Use py::isinstance(obj) instead") \ bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); } \ static bool _check(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); } +#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \ + PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ + Name(handle h, bool borrowed) : Name(object(h, borrowed)) { } \ + /* This is deliberately not 'explicit' to allow implicit conversion from object: */ \ + Name(const object &o) : Parent(ConvertFun(o.ptr()), false) { if (!m_ptr) throw error_already_set(); } + #define PYBIND11_OBJECT(Name, Parent, CheckFun) \ - PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ) + PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \ + Name(handle h, bool borrowed) : Parent(h, borrowed) { } \ + /* This is deliberately not 'explicit' to allow implicit conversion from object: */ \ + Name(const object &o) : Parent(o) { } #define PYBIND11_OBJECT_DEFAULT(Name, Parent, CheckFun) \ PYBIND11_OBJECT(Name, Parent, CheckFun) \ @@ -411,26 +414,9 @@ NAMESPACE_END(detail) class iterator : public object { public: - PYBIND11_OBJECT_CVT(iterator, object, PyIter_Check, value = object(); ready = false) - iterator() : object(), value(object()), ready(false) { } - iterator(const iterator& it) : object(it), value(it.value), ready(it.ready) { } - iterator(iterator&& it) : object(std::move(it)), value(std::move(it.value)), ready(it.ready) { } - - /** Caveat: this copy constructor does not (and cannot) clone the internal + /** Caveat: copying an iterator does not (and cannot) clone the internal state of the Python iterable */ - iterator &operator=(const iterator &it) { - (void) object::operator=(it); - value = it.value; - ready = it.ready; - return *this; - } - - iterator &operator=(iterator &&it) noexcept { - (void) object::operator=(std::move(it)); - value = std::move(it.value); - ready = it.ready; - return *this; - } + PYBIND11_OBJECT_DEFAULT(iterator, object, PyIter_Check) iterator& operator++() { if (m_ptr) @@ -465,8 +451,8 @@ private: void advance() { value = object(PyIter_Next(m_ptr), false); } private: - object value; - bool ready; + object value = {}; + bool ready = false; }; class iterable : public object { @@ -478,7 +464,7 @@ class bytes; class str : public object { public: - PYBIND11_OBJECT_DEFAULT(str, object, detail::PyUnicode_Check_Permissive) + PYBIND11_OBJECT_CVT(str, object, detail::PyUnicode_Check_Permissive, raw_str) str(const char *c, size_t n) : object(PyUnicode_FromStringAndSize(c, (ssize_t) n), false) { @@ -486,7 +472,7 @@ public: } // 'explicit' is explicitly omitted from the following constructors to allow implicit conversion to py::str from C++ string-like objects - str(const char *c) + str(const char *c = "") : object(PyUnicode_FromString(c), false) { if (!m_ptr) pybind11_fail("Could not allocate string object!"); } @@ -495,6 +481,8 @@ public: explicit str(const bytes &b); + explicit str(handle h) : object(raw_str(h.ptr()), false) { } + operator std::string() const { object temp = *this; if (PyUnicode_Check(m_ptr)) { @@ -513,6 +501,18 @@ public: str format(Args &&...args) const { return attr("format")(std::forward(args)...); } + +private: + /// Return string representation -- always returns a new reference, even if already a str + static PyObject *raw_str(PyObject *op) { + PyObject *str_value = PyObject_Str(op); +#if PY_MAJOR_VERSION < 3 + if (!str_value) throw error_already_set(); + PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr); + Py_XDECREF(str_value); str_value = unicode; +#endif + return str_value; + } }; inline namespace literals { @@ -522,10 +522,10 @@ inline str operator"" _s(const char *s, size_t size) { return {s, size}; } class bytes : public object { public: - PYBIND11_OBJECT_DEFAULT(bytes, object, PYBIND11_BYTES_CHECK) + PYBIND11_OBJECT(bytes, object, PYBIND11_BYTES_CHECK) // Allow implicit conversion: - bytes(const char *c) + bytes(const char *c = "") : object(PYBIND11_BYTES_FROM_STRING(c), false) { if (!m_ptr) pybind11_fail("Could not allocate bytes object!"); } @@ -585,15 +585,25 @@ public: class bool_ : public object { public: - PYBIND11_OBJECT_DEFAULT(bool_, object, PyBool_Check) + PYBIND11_OBJECT_CVT(bool_, object, PyBool_Check, raw_bool) + bool_() : object(Py_False, true) { } // Allow implicit conversion from and to `bool`: bool_(bool value) : object(value ? Py_True : Py_False, true) { } operator bool() const { return m_ptr && PyLong_AsLong(m_ptr) != 0; } + +private: + /// Return the truth value of an object -- always returns a new reference + static PyObject *raw_bool(PyObject *op) { + const auto value = PyObject_IsTrue(op); + if (value == -1) return nullptr; + return handle(value ? Py_True : Py_False).inc_ref().ptr(); + } }; class int_ : public object { public: - PYBIND11_OBJECT_DEFAULT(int_, object, PYBIND11_LONG_CHECK) + PYBIND11_OBJECT_CVT(int_, object, PYBIND11_LONG_CHECK, PyNumber_Long) + int_() : object(PyLong_FromLong(0), false) { } // Allow implicit conversion from C++ integral types: template ::value, int> = 0> @@ -631,12 +641,12 @@ public: class float_ : public object { public: - PYBIND11_OBJECT_DEFAULT(float_, object, PyFloat_Check) + PYBIND11_OBJECT_CVT(float_, object, PyFloat_Check, PyNumber_Float) // Allow implicit conversion from float/double: float_(float value) : object(PyFloat_FromDouble((double) value), false) { if (!m_ptr) pybind11_fail("Could not allocate float object!"); } - float_(double value) : object(PyFloat_FromDouble((double) value), false) { + float_(double value = .0) : object(PyFloat_FromDouble((double) value), false) { if (!m_ptr) pybind11_fail("Could not allocate float object!"); } operator float() const { return (float) PyFloat_AsDouble(m_ptr); } @@ -685,7 +695,7 @@ public: class tuple : public object { public: - PYBIND11_OBJECT(tuple, object, PyTuple_Check) + PYBIND11_OBJECT_CVT(tuple, object, PyTuple_Check, PySequence_Tuple) explicit tuple(size_t size = 0) : object(PyTuple_New((ssize_t) size), false) { if (!m_ptr) pybind11_fail("Could not allocate tuple object!"); } @@ -695,7 +705,7 @@ public: class dict : public object { public: - PYBIND11_OBJECT(dict, object, PyDict_Check) + PYBIND11_OBJECT_CVT(dict, object, PyDict_Check, raw_dict) dict() : object(PyDict_New(), false) { if (!m_ptr) pybind11_fail("Could not allocate dict object!"); } @@ -711,6 +721,14 @@ public: void clear() const { PyDict_Clear(ptr()); } bool contains(handle key) const { return PyDict_Contains(ptr(), key.ptr()) == 1; } bool contains(const char *key) const { return PyDict_Contains(ptr(), pybind11::str(key).ptr()) == 1; } + +private: + /// Call the `dict` Python type -- always returns a new reference + static PyObject *raw_dict(PyObject *op) { + if (PyDict_Check(op)) + return handle(op).inc_ref().ptr(); + return PyObject_CallFunctionObjArgs((PyObject *) &PyDict_Type, op, nullptr); + } }; class sequence : public object { @@ -722,7 +740,7 @@ public: class list : public object { public: - PYBIND11_OBJECT(list, object, PyList_Check) + PYBIND11_OBJECT_CVT(list, object, PyList_Check, PySequence_List) explicit list(size_t size = 0) : object(PyList_New((ssize_t) size), false) { if (!m_ptr) pybind11_fail("Could not allocate list object!"); } @@ -736,7 +754,7 @@ class kwargs : public dict { PYBIND11_OBJECT_DEFAULT(kwargs, dict, PyDict_Check) class set : public object { public: - PYBIND11_OBJECT(set, object, PySet_Check) + PYBIND11_OBJECT_CVT(set, object, PySet_Check, PySet_New) set() : object(PySet_New(nullptr), false) { if (!m_ptr) pybind11_fail("Could not allocate set object!"); } @@ -797,7 +815,7 @@ public: pybind11_fail("Unable to create memoryview from buffer descriptor"); } - PYBIND11_OBJECT_DEFAULT(memoryview, object, PyMemoryView_Check) + PYBIND11_OBJECT_CVT(memoryview, object, PyMemoryView_Check, PyMemoryView_FromObject) }; inline size_t len(handle h) { @@ -807,6 +825,17 @@ inline size_t len(handle h) { return (size_t) result; } +inline str repr(handle h) { + PyObject *str_value = PyObject_Repr(h.ptr()); + if (!str_value) throw error_already_set(); +#if PY_MAJOR_VERSION < 3 + PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr); + Py_XDECREF(str_value); str_value = unicode; + if (!str_value) throw error_already_set(); +#endif + return {str_value, false}; +} + NAMESPACE_BEGIN(detail) template iterator object_api::begin() const { return {PyObject_GetIter(derived().ptr()), false}; } template iterator object_api::end() const { return {nullptr, false}; } @@ -820,24 +849,7 @@ template template bool object_api::contains(T &&key } template -pybind11::str object_api::str() const { - PyObject *str_value = PyObject_Str(derived().ptr()); -#if PY_MAJOR_VERSION < 3 - PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr); - Py_XDECREF(str_value); str_value = unicode; -#endif - return {str_value, false}; -} - -template -pybind11::str object_api::repr() const { - PyObject *str_value = PyObject_Repr(derived().ptr()); -#if PY_MAJOR_VERSION < 3 - PyObject *unicode = PyUnicode_FromEncodedObject(str_value, "utf-8", nullptr); - Py_XDECREF(str_value); str_value = unicode; -#endif - return {str_value, false}; -} +pybind11::str object_api::str() const { return pybind11::str(derived()); } template handle object_api::get_type() const { return (PyObject *) Py_TYPE(derived().ptr()); } diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index ed8ab5cbb..fc058ef25 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -248,7 +248,7 @@ template<> struct type_caster NAMESPACE_END(detail) inline std::ostream &operator<<(std::ostream &os, const handle &obj) { - os << (std::string) obj.str(); + os << (std::string) str(obj); return os; } diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 8f680c071..20cb36269 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -196,14 +196,14 @@ py::list print_format_descriptors() { py::list print_dtypes() { const auto dtypes = { - py::dtype::of().str(), - py::dtype::of().str(), - py::dtype::of().str(), - py::dtype::of().str(), - py::dtype::of().str(), - py::dtype::of().str(), - py::dtype::of().str(), - py::dtype::of().str() + py::str(py::dtype::of()), + py::str(py::dtype::of()), + py::str(py::dtype::of()), + py::str(py::dtype::of()), + py::str(py::dtype::of()), + py::str(py::dtype::of()), + py::str(py::dtype::of()), + py::str(py::dtype::of()) }; auto l = py::list(); for (const auto &s : dtypes) { diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 97cdc2e87..05ba4af9e 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -153,8 +153,8 @@ public: } void test_print(const py::object& obj) { - py::print(obj.str()); - py::print(obj.repr()); + py::print(py::str(obj)); + py::print(py::repr(obj)); } static int value; @@ -321,4 +321,46 @@ test_initializer python_types([](py::module &m) { m.attr("has_optional") = py::cast(has_optional); m.attr("has_exp_optional") = py::cast(has_exp_optional); + + m.def("test_default_constructors", []() { + return py::dict( + "str"_a=py::str(), + "bool"_a=py::bool_(), + "int"_a=py::int_(), + "float"_a=py::float_(), + "tuple"_a=py::tuple(), + "list"_a=py::list(), + "dict"_a=py::dict(), + "set"_a=py::set() + ); + }); + + m.def("test_converting_constructors", [](py::dict d) { + return py::dict( + "str"_a=py::str(d["str"]), + "bool"_a=py::bool_(d["bool"]), + "int"_a=py::int_(d["int"]), + "float"_a=py::float_(d["float"]), + "tuple"_a=py::tuple(d["tuple"]), + "list"_a=py::list(d["list"]), + "dict"_a=py::dict(d["dict"]), + "set"_a=py::set(d["set"]), + "memoryview"_a=py::memoryview(d["memoryview"]) + ); + }); + + m.def("test_cast_functions", [](py::dict d) { + // When converting between Python types, obj.cast() should be the same as T(obj) + return py::dict( + "str"_a=d["str"].cast(), + "bool"_a=d["bool"].cast(), + "int"_a=d["int"].cast(), + "float"_a=d["float"].cast(), + "tuple"_a=d["tuple"].cast(), + "list"_a=d["list"].cast(), + "dict"_a=d["dict"].cast(), + "set"_a=d["set"].cast(), + "memoryview"_a=d["memoryview"].cast() + ); + }); }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index b77a95b04..0b24c138f 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -330,3 +330,29 @@ def test_exp_optional(): assert test_nullopt_exp(None) == 42 assert test_nullopt_exp(42) == 42 assert test_nullopt_exp(43) == 43 + + +def test_constructors(): + """C++ default and converting constructors are equivalent to type calls in Python""" + from pybind11_tests import (test_default_constructors, test_converting_constructors, + test_cast_functions) + + types = [str, bool, int, float, tuple, list, dict, set] + expected = {t.__name__: t() for t in types} + assert test_default_constructors() == expected + + data = { + str: 42, + bool: "Not empty", + int: "42", + float: "+1e3", + tuple: range(3), + list: range(3), + dict: [("two", 2), ("one", 1), ("three", 3)], + set: [4, 4, 5, 6, 6, 6], + memoryview: b'abc' + } + inputs = {k.__name__: v for k, v in data.items()} + expected = {k.__name__: k(v) for k, v in data.items()} + assert test_converting_constructors(inputs) == expected + assert test_cast_functions(inputs) == expected