pytypes: Add Gotchas section about default-constructed wrapper types and py::none() (#2362)

This commit is contained in:
Eric Cousineau 2020-09-04 19:26:57 -04:00 committed by GitHub
parent 72b06b86b3
commit 44fa79ca80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 45 additions and 0 deletions

View File

@ -15,6 +15,11 @@ Available types include :class:`handle`, :class:`object`, :class:`bool_`,
:class:`iterable`, :class:`iterator`, :class:`function`, :class:`buffer`, :class:`iterable`, :class:`iterator`, :class:`function`, :class:`buffer`,
:class:`array`, and :class:`array_t`. :class:`array`, and :class:`array_t`.
.. warning::
Be sure to review the :ref:`pytypes_gotchas` before using this heavily in
your C++ API.
Casting back and forth Casting back and forth
====================== ======================
@ -178,3 +183,25 @@ Python exceptions from wrapper classes will be thrown as a ``py::error_already_s
See :ref:`Handling exceptions from Python in C++ See :ref:`Handling exceptions from Python in C++
<handling_python_exceptions_cpp>` for more information on handling exceptions <handling_python_exceptions_cpp>` for more information on handling exceptions
raised when calling C++ wrapper classes. raised when calling C++ wrapper classes.
.. _pytypes_gotchas:
Gotchas
=======
Default-Constructed Wrappers
----------------------------
When a wrapper type is default-constructed, it is **not** a valid Python object (i.e. it is not ``py::none()``). It is simply the same as
``PyObject*`` null pointer. To check for this, use
``static_cast<bool>(my_wrapper)``.
Assigning py::none() to wrappers
--------------------------------
You may be tempted to use types like ``py::str`` and ``py::dict`` in C++
signatures (either pure C++, or in bound signatures), and assign them default
values of ``py::none()``. However, in a best case scenario, it will fail fast
because ``None`` is not convertible to that type (e.g. ``py::dict``), or in a
worse case scenario, it will silently work but corrupt the types you want to
work with (e.g. ``py::str(py::none())`` will yield ``"None"`` in Python).

View File

@ -324,6 +324,16 @@ TEST_SUBMODULE(pytypes, m) {
return a[py::slice(0, -1, 2)]; return a[py::slice(0, -1, 2)];
}); });
// See #2361
m.def("issue2361_str_implicit_copy_none", []() {
py::str is_this_none = py::none();
return is_this_none;
});
m.def("issue2361_dict_implicit_copy_none", []() {
py::dict is_this_none = py::none();
return is_this_none;
});
m.def("test_memoryview_object", [](py::buffer b) { m.def("test_memoryview_object", [](py::buffer b) {
return py::memoryview(b); return py::memoryview(b);
}); });

View File

@ -326,6 +326,14 @@ def test_list_slicing():
assert li[::2] == m.test_list_slicing(li) assert li[::2] == m.test_list_slicing(li)
def test_issue2361():
# See issue #2361
assert m.issue2361_str_implicit_copy_none() == "None"
with pytest.raises(TypeError) as excinfo:
assert m.issue2361_dict_implicit_copy_none()
assert "'NoneType' object is not iterable" in str(excinfo.value)
@pytest.mark.parametrize('method, args, fmt, expected_view', [ @pytest.mark.parametrize('method, args, fmt, expected_view', [
(m.test_memoryview_object, (b'red',), 'B', b'red'), (m.test_memoryview_object, (b'red',), 'B', b'red'),
(m.test_memoryview_buffer_info, (b'green',), 'B', b'green'), (m.test_memoryview_buffer_info, (b'green',), 'B', b'green'),