Commit e18bc02f by Dean Moldovan Committed by Wenzel Jakob

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<T>() works as expected when T is a Python type

`obj.cast<T>()` should be the same as `T(obj)`, i.e. it should convert
the given object to a different Python type. However, `obj.cast<T>()`
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<T>()` 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.
parent b4498ef4
...@@ -28,7 +28,7 @@ Breaking changes queued for v2.0.0 (Not yet released) ...@@ -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 (now uses prefix increment operator); it now also accepts iterators with
different begin/end types as long as they are equality comparable. different begin/end types as long as they are equality comparable.
* ``arg()`` now accepts a wider range of argument types for default values * ``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 support for registering structured dtypes via ``PYBIND11_NUMPY_DTYPE()`` macro.
* Added ``PYBIND11_STR_TYPE`` macro which maps to the ``builtins.str`` type. * Added ``PYBIND11_STR_TYPE`` macro which maps to the ``builtins.str`` type.
* Added a simplified ``buffer_info`` constructor for 1-dimensional buffers. * Added a simplified ``buffer_info`` constructor for 1-dimensional buffers.
......
...@@ -265,9 +265,9 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> { ...@@ -265,9 +265,9 @@ template <> struct process_attribute<arg_v> : process_attribute_default<arg_v> {
auto descr = "'" + std::string(a.name) + ": " + a.type + "'"; auto descr = "'" + std::string(a.name) + ": " + a.type + "'";
if (r->class_) { if (r->class_) {
if (r->name) 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 else
descr += " in method of '" + (std::string) r->class_.str() + "'"; descr += " in method of '" + (std::string) str(r->class_) + "'";
} else if (r->name) { } else if (r->name) {
descr += " in function named '" + (std::string) r->name + "'"; descr += " in function named '" + (std::string) r->name + "'";
} }
......
...@@ -132,7 +132,7 @@ PYBIND11_NOINLINE inline std::string error_string() { ...@@ -132,7 +132,7 @@ PYBIND11_NOINLINE inline std::string error_string() {
errorString += ": "; errorString += ": ";
} }
if (scope.value) if (scope.value)
errorString += (std::string) handle(scope.value).str(); errorString += (std::string) str(scope.value);
PyErr_NormalizeException(&scope.type, &scope.value, &scope.trace); PyErr_NormalizeException(&scope.type, &scope.value, &scope.trace);
...@@ -1056,7 +1056,7 @@ template <typename T, typename SFINAE> type_caster<T, SFINAE> &load_type(type_ca ...@@ -1056,7 +1056,7 @@ template <typename T, typename SFINAE> type_caster<T, SFINAE> &load_type(type_ca
throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)"); throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)");
#else #else
throw cast_error("Unable to cast Python instance of type " + throw cast_error("Unable to cast Python instance of type " +
(std::string) handle.get_type().str() + " to C++ type '" + type_id<T>() + "''"); (std::string) str(handle.get_type()) + " to C++ type '" + type_id<T>() + "''");
#endif #endif
} }
return conv; return conv;
...@@ -1070,16 +1070,20 @@ template <typename T> make_caster<T> load_type(const handle &handle) { ...@@ -1070,16 +1070,20 @@ template <typename T> make_caster<T> load_type(const handle &handle) {
NAMESPACE_END(detail) NAMESPACE_END(detail)
template <typename T> T cast(const handle &handle) { template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::value, int> = 0>
T cast(const handle &handle) {
static_assert(!detail::cast_is_temporary_value_reference<T>::value, static_assert(!detail::cast_is_temporary_value_reference<T>::value,
"Unable to cast type to reference: value is local to type caster"); "Unable to cast type to reference: value is local to type caster");
using type_caster = detail::make_caster<T>; using type_caster = detail::make_caster<T>;
return detail::load_type<T>(handle).operator typename type_caster::template cast_op_type<T>(); return detail::load_type<T>(handle).operator typename type_caster::template cast_op_type<T>();
} }
template <typename T> object cast(const T &value, template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
return_value_policy policy = return_value_policy::automatic_reference, T cast(const handle &handle) { return {handle, true}; }
handle parent = handle()) {
template <typename T, detail::enable_if_t<!detail::is_pyobject<T>::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) if (policy == return_value_policy::automatic)
policy = std::is_pointer<T>::value ? return_value_policy::take_ownership : return_value_policy::copy; policy = std::is_pointer<T>::value ? return_value_policy::take_ownership : return_value_policy::copy;
else if (policy == return_value_policy::automatic_reference) else if (policy == return_value_policy::automatic_reference)
...@@ -1097,7 +1101,7 @@ detail::enable_if_t<detail::move_always<T>::value || detail::move_if_unreference ...@@ -1097,7 +1101,7 @@ detail::enable_if_t<detail::move_always<T>::value || detail::move_if_unreference
throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references" throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references"
" (compile in debug mode for details)"); " (compile in debug mode for details)");
#else #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<T>() + " instance: instance has multiple references"); " instance to C++ " + type_id<T>() + " instance: instance has multiple references");
#endif #endif
...@@ -1274,7 +1278,7 @@ public: ...@@ -1274,7 +1278,7 @@ public:
int _[] = { 0, (process(args_list, std::forward<Ts>(values)), 0)... }; int _[] = { 0, (process(args_list, std::forward<Ts>(values)), 0)... };
ignore_unused(_); ignore_unused(_);
m_args = object(PyList_AsTuple(args_list.ptr()), false); m_args = std::move(args_list);
} }
const tuple &args() const & { return m_args; } const tuple &args() const & { return m_args; }
...@@ -1336,7 +1340,7 @@ private: ...@@ -1336,7 +1340,7 @@ private:
#if defined(NDEBUG) #if defined(NDEBUG)
multiple_values_error(); multiple_values_error();
#else #else
multiple_values_error(k.first.str()); multiple_values_error(str(k.first));
#endif #endif
} }
m_kwargs[k.first] = k.second; m_kwargs[k.first] = k.second;
......
...@@ -111,7 +111,6 @@ ...@@ -111,7 +111,6 @@
#define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyBytes_FromStringAndSize #define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyBytes_FromStringAndSize
#define PYBIND11_BYTES_AS_STRING_AND_SIZE PyBytes_AsStringAndSize #define PYBIND11_BYTES_AS_STRING_AND_SIZE PyBytes_AsStringAndSize
#define PYBIND11_BYTES_AS_STRING PyBytes_AsString #define PYBIND11_BYTES_AS_STRING PyBytes_AsString
#define PYBIND11_BYTES_CHECK PyBytes_Check
#define PYBIND11_LONG_CHECK(o) PyLong_Check(o) #define PYBIND11_LONG_CHECK(o) PyLong_Check(o)
#define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o) #define PYBIND11_LONG_AS_LONGLONG(o) PyLong_AsLongLong(o)
#define PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o) PyLong_AsUnsignedLongLong(o) #define PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o) PyLong_AsUnsignedLongLong(o)
...@@ -130,7 +129,6 @@ ...@@ -130,7 +129,6 @@
#define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyString_FromStringAndSize #define PYBIND11_BYTES_FROM_STRING_AND_SIZE PyString_FromStringAndSize
#define PYBIND11_BYTES_AS_STRING_AND_SIZE PyString_AsStringAndSize #define PYBIND11_BYTES_AS_STRING_AND_SIZE PyString_AsStringAndSize
#define PYBIND11_BYTES_AS_STRING PyString_AsString #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_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_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)) #define PYBIND11_LONG_AS_UNSIGNED_LONGLONG(o) (PyInt_Check(o) ? (unsigned long long) PyLong_AsUnsignedLong(o) : PyLong_AsUnsignedLongLong(o))
......
...@@ -540,10 +540,12 @@ protected: ...@@ -540,10 +540,12 @@ protected:
template <typename T, int ExtraFlags = array::forcecast> class array_t : public array { template <typename T, int ExtraFlags = array::forcecast> class array_t : public array {
public: public:
PYBIND11_OBJECT_CVT(array_t, array, is_non_null, m_ptr = ensure_(m_ptr));
array_t() : array() { } 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) { } explicit array_t(const buffer_info& info) : array(info) { }
array_t(const std::vector<size_t> &shape, array_t(const std::vector<size_t> &shape,
...@@ -588,8 +590,6 @@ public: ...@@ -588,8 +590,6 @@ public:
return *(static_cast<T*>(array::mutable_data()) + byte_offset(size_t(index)...) / itemsize()); return *(static_cast<T*>(array::mutable_data()) + byte_offset(size_t(index)...) / itemsize());
} }
static bool is_non_null(PyObject *ptr) { return ptr != nullptr; }
static PyObject *ensure_(PyObject *ptr) { static PyObject *ensure_(PyObject *ptr) {
if (ptr == nullptr) if (ptr == nullptr)
return nullptr; return nullptr;
......
...@@ -504,7 +504,7 @@ protected: ...@@ -504,7 +504,7 @@ protected:
msg += "\nInvoked with: "; msg += "\nInvoked with: ";
tuple args_(args, true); tuple args_(args, true);
for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) { for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) {
msg += static_cast<std::string>(static_cast<object>(args_[ti]).str()); msg += static_cast<std::string>(pybind11::str(args_[ti]));
if ((ti + 1) != args_.size() ) if ((ti + 1) != args_.size() )
msg += ", "; msg += ", ";
} }
...@@ -665,11 +665,9 @@ protected: ...@@ -665,11 +665,9 @@ protected:
#endif #endif
size_t num_bases = rec->bases.size(); size_t num_bases = rec->bases.size();
tuple bases(num_bases); auto bases = tuple(rec->bases);
for (size_t i = 0; i < num_bases; ++i)
bases[i] = rec->bases[i];
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)); : std::string(rec->name));
char *tp_doc = nullptr; char *tp_doc = nullptr;
...@@ -1470,7 +1468,7 @@ NAMESPACE_BEGIN(detail) ...@@ -1470,7 +1468,7 @@ NAMESPACE_BEGIN(detail)
PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) { PYBIND11_NOINLINE inline void print(tuple args, dict kwargs) {
auto strings = tuple(args.size()); auto strings = tuple(args.size());
for (size_t i = 0; i < args.size(); ++i) { 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 sep = kwargs.contains("sep") ? kwargs["sep"] : cast(" ");
auto line = sep.attr("join")(strings); auto line = sep.attr("join")(strings);
...@@ -1654,7 +1652,7 @@ inline function get_type_overload(const void *this_ptr, const detail::type_info ...@@ -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 */ /* Don't call dispatch code if invoked from overridden function */
PyFrameObject *frame = PyThreadState_Get()->frame; 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) { frame->f_code->co_argcount > 0) {
PyFrame_FastToLocals(frame); PyFrame_FastToLocals(frame);
PyObject *self_caller = PyDict_GetItem( PyObject *self_caller = PyDict_GetItem(
......
...@@ -248,7 +248,7 @@ template<> struct type_caster<std::experimental::nullopt_t> ...@@ -248,7 +248,7 @@ template<> struct type_caster<std::experimental::nullopt_t>
NAMESPACE_END(detail) NAMESPACE_END(detail)
inline std::ostream &operator<<(std::ostream &os, const handle &obj) { inline std::ostream &operator<<(std::ostream &os, const handle &obj) {
os << (std::string) obj.str(); os << (std::string) str(obj);
return os; return os;
} }
......
...@@ -196,14 +196,14 @@ py::list print_format_descriptors() { ...@@ -196,14 +196,14 @@ py::list print_format_descriptors() {
py::list print_dtypes() { py::list print_dtypes() {
const auto dtypes = { const auto dtypes = {
py::dtype::of<SimpleStruct>().str(), py::str(py::dtype::of<SimpleStruct>()),
py::dtype::of<PackedStruct>().str(), py::str(py::dtype::of<PackedStruct>()),
py::dtype::of<NestedStruct>().str(), py::str(py::dtype::of<NestedStruct>()),
py::dtype::of<PartialStruct>().str(), py::str(py::dtype::of<PartialStruct>()),
py::dtype::of<PartialNestedStruct>().str(), py::str(py::dtype::of<PartialNestedStruct>()),
py::dtype::of<StringStruct>().str(), py::str(py::dtype::of<StringStruct>()),
py::dtype::of<EnumStruct>().str(), py::str(py::dtype::of<EnumStruct>()),
py::dtype::of<StructWithUglyNames>().str() py::str(py::dtype::of<StructWithUglyNames>())
}; };
auto l = py::list(); auto l = py::list();
for (const auto &s : dtypes) { for (const auto &s : dtypes) {
......
...@@ -153,8 +153,8 @@ public: ...@@ -153,8 +153,8 @@ public:
} }
void test_print(const py::object& obj) { void test_print(const py::object& obj) {
py::print(obj.str()); py::print(py::str(obj));
py::print(obj.repr()); py::print(py::repr(obj));
} }
static int value; static int value;
...@@ -321,4 +321,46 @@ test_initializer python_types([](py::module &m) { ...@@ -321,4 +321,46 @@ test_initializer python_types([](py::module &m) {
m.attr("has_optional") = py::cast(has_optional); m.attr("has_optional") = py::cast(has_optional);
m.attr("has_exp_optional") = py::cast(has_exp_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<T>() should be the same as T(obj)
return py::dict(
"str"_a=d["str"].cast<py::str>(),
"bool"_a=d["bool"].cast<py::bool_>(),
"int"_a=d["int"].cast<py::int_>(),
"float"_a=d["float"].cast<py::float_>(),
"tuple"_a=d["tuple"].cast<py::tuple>(),
"list"_a=d["list"].cast<py::list>(),
"dict"_a=d["dict"].cast<py::dict>(),
"set"_a=d["set"].cast<py::set>(),
"memoryview"_a=d["memoryview"].cast<py::memoryview>()
);
});
}); });
...@@ -330,3 +330,29 @@ def test_exp_optional(): ...@@ -330,3 +330,29 @@ def test_exp_optional():
assert test_nullopt_exp(None) == 42 assert test_nullopt_exp(None) == 42
assert test_nullopt_exp(42) == 42 assert test_nullopt_exp(42) == 42
assert test_nullopt_exp(43) == 43 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment