diff --git a/docs/advanced/pycpp/object.rst b/docs/advanced/pycpp/object.rst index 07525d0d..c6c3b1b7 100644 --- a/docs/advanced/pycpp/object.rst +++ b/docs/advanced/pycpp/object.rst @@ -15,6 +15,11 @@ Available types include :class:`handle`, :class:`object`, :class:`bool_`, :class:`iterable`, :class:`iterator`, :class:`function`, :class:`buffer`, :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 ====================== @@ -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++ ` for more information on handling exceptions 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(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). diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 0f8d5641..925d6ffd 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -324,6 +324,16 @@ TEST_SUBMODULE(pytypes, m) { 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) { return py::memoryview(b); }); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 95cc94af..277c170e 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -326,6 +326,14 @@ def test_list_slicing(): 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', [ (m.test_memoryview_object, (b'red',), 'B', b'red'), (m.test_memoryview_buffer_info, (b'green',), 'B', b'green'),