Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make PYBIND11_OBJECT_CVT only convert if the type check fails #979

Merged
merged 1 commit into from
Aug 4, 2017

Conversation

jagerman
Copy link
Member

@jagerman jagerman commented Aug 4, 2017

Currently types that are capable of conversion always call their convert
function when invoked with a py::object which is actually the correct
type. This means that code such as py::cast<py::list>(obj) and
py::list l(obj.attr("list")) make copies, which seems an oversight
rather than an intentional feature.

While at first glance there might be something behind having
py::list(obj) make a copy (as it would in Python), this would be
inconsistent when you dig a little deeper because py::list(l)
doesn't make a copy for an existing py::list l.

It is possible to work around the issue using a
reinterpret_borrow<list>(o) (and this commit fixes one place, in
embed.h, that does so), but that seems a misuse of
reinterpret_borrow, which is really supposed to be just for dealing
with raw python-returned values, not py::object-derived wrappers
which are supposed to be higher level.

This changes the constructor of such converting types (i.e. anything
using PYBIND11_OBJECT_CVT -- str, bool_, int_, float_, tuple,
dict, list, set, memoryview) to reference rather than copy when
the check function passes.

(Cc and thanks to @apollo13, who brought my attention to this issue).

Copy link
Member

@dean0x7d dean0x7d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, obj.cast<py::list>() making a copy was an oversight on my part. The fix applied in this PR results in a much more natural syntax.

I'm a bit sad about py::list(obj) not being a guaranteed copy (inconsistency with Python), but as you mentioned, it's not really viable and would create an inconsistency within C++ which is worse than having a C++ <-> Python inconsistency.

@@ -99,7 +99,7 @@ inline void initialize_interpreter(bool init_signal_handlers = true) {
Py_InitializeEx(init_signal_handlers ? 1 : 0);

// Make .py files in the working directory available by default
auto sys_path = reinterpret_borrow<list>(module::import("sys").attr("path"));
list sys_path(module::import("sys").attr("path"));
sys_path.append(".");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for an intermediate variable anymore: module::import("sys").attr("path").cast<py::list>().append("."); Makes it more obvious that the path is being modified in place.

@@ -731,7 +731,12 @@ NAMESPACE_END(detail)
#define PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, ConvertFun) \
PYBIND11_OBJECT_COMMON(Name, Parent, CheckFun) \
/* This is deliberately not 'explicit' to allow implicit conversion from object: */ \
Name(const object &o) : Parent(ConvertFun(o.ptr()), stolen_t{}) { if (!m_ptr) throw error_already_set(); }
Name(const object &o) \
: Parent(o && CheckFun(o.ptr()) ? o.inc_ref() : handle(ConvertFun(o.ptr())), stolen_t{}) \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best not to duplicate the check_() function (and similarly below):

Parent(check_(o) ? o.inc_ref().ptr() : ConvertFun(o.ptr()), stolen_t{})

Currently types that are capable of conversion always call their convert
function when invoked with a `py::object` which is actually the correct
type.  This means that code such as `py::cast<py::list>(obj)` and
`py::list l(obj.attr("list"))` make copies, which was an oversight
rather than an intentional feature.

While at first glance there might be something behind having
`py::list(obj)` make a copy (as it would in Python), this would be
inconsistent when you dig a little deeper because `py::list(l)`
*doesn't* make a copy for an existing `py::list l`, and having an
inconsistency within C++ would be worse than a C++ <-> Python
inconsistency.

It is possible to get around the copying using a
`reinterpret_borrow<list>(o)` (and this commit fixes one place, in
`embed.h`, that does so), but that seems a misuse of
`reinterpret_borrow`, which is really supposed to be just for dealing
with raw python-returned values, not `py::object`-derived wrappers which
are supposed to be higher level.

This changes the constructor of such converting types (i.e. anything
using PYBIND11_OBJECT_CVT -- `str`, `bool_`, `int_`, `float_`, `tuple`,
`dict`, `list`, `set`, `memoryview`) to reference rather than copy when
the check function passes.

It also adds an `object &&` constructor that is slightly more efficient
by avoiding an inc_ref when the check function passes.
@jagerman jagerman merged commit 373da82 into pybind:master Aug 4, 2017
@dean0x7d dean0x7d modified the milestone: v2.2 Aug 13, 2017
@jagerman jagerman deleted the object-convert-check branch August 14, 2017 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants