From 2f7b356229edd4499480410c75c29193319ffed5 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 4 Aug 2020 11:00:38 -0400 Subject: [PATCH 1/7] feat: type() --- include/pybind11/cast.h | 21 +++++++++++++++++++++ tests/test_class.cpp | 17 +++++++++++++++++ tests/test_class.py | 17 +++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index be62610a72..54bad04236 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -2204,6 +2204,27 @@ object object_api::call(Args &&...args) const { PYBIND11_NAMESPACE_END(detail) + +/** \ingroup python_builtins + \rst + Return the registered type object for a C++ class, given as a template parameter. + py::type() returns the Python type object previously registered for T. +\endrst */ +template +handle type() { + static_assert( + std::is_base_of>::value, + "This currently only works for registered C++ types. The type here is most likely type converted (using type_caster)." + ); + + return detail::get_type_handle(typeid(T), true); +} + +inline handle type(handle h) { + PyObject* obj = (PyObject *) Py_TYPE(h.ptr()); + return handle(obj); +} + #define PYBIND11_MAKE_OPAQUE(...) \ namespace pybind11 { namespace detail { \ template<> class type_caster<__VA_ARGS__> : public type_caster_base<__VA_ARGS__> { }; \ diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 5369cb064c..d57a0acb20 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -134,6 +134,23 @@ TEST_SUBMODULE(class_, m) { ); }); + struct Invalid {}; + + // test_type + m.def("check_type", [](int category) { + // Currently not supported (via a fail at compile time) + // if (category == 2) + // return py::type(); + if (category == 1) + return py::type(); + else + return py::type(); + }); + + m.def("compute_type", [](py::handle h) { + return py::type(h); + }); + // test_mismatched_holder struct MismatchBase1 { }; struct MismatchDerived1 : MismatchBase1 { }; diff --git a/tests/test_class.py b/tests/test_class.py index 4214fe79d7..bd2bf0de61 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -26,6 +26,23 @@ def test_instance(msg): assert cstats.alive() == 0 +def test_type(): + assert m.check_type(1) == m.DerivedClass1 + with pytest.raises(RuntimeError) as execinfo: + m.check_type(0) + + assert 'pybind11::detail::get_type_info: unable to find type info' in str(execinfo.value) + assert 'Invalid' in str(execinfo.value) + + # Currently not supported + # assert m.check_type(2) == int + + +def test_type_py(): + assert m.compute_type(1) == int + assert m.compute_type(m.DerivedClass1()) == m.DerivedClass1 + + def test_docstrings(doc): assert doc(UserType) == "A `py::class_` type for testing" assert UserType.__name__ == "UserType" From 47c548560157931553d09aa1318c39fff35f3583 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 8 Sep 2020 11:56:43 -0400 Subject: [PATCH 2/7] refactor: using py::type as class --- docs/advanced/classes.rst | 14 ++++++++++++++ include/pybind11/cast.h | 15 +++------------ include/pybind11/pytypes.h | 17 +++++++++++++++++ tests/test_class.cpp | 6 +++--- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index f4efc68f8b..53a805551c 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -1232,3 +1232,17 @@ appropriate derived-class pointer (e.g. using more complete example, including a demonstration of how to provide automatic downcasting for an entire class hierarchy without writing one get() function for each class. + +Accessing the type object +========================= + +You can get the type object from a C++ class that has already been registered using: + +.. code-block:: python + + auto T_py = py::type::of(); + +You can directly use ``py::type(ob)`` to get the type object from any python +object, just like ``type(ob)`` in Python. + +.. versionadded:: 2.6 diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 54bad04236..3b42c15314 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -2205,25 +2205,16 @@ object object_api::call(Args &&...args) const { PYBIND11_NAMESPACE_END(detail) -/** \ingroup python_builtins - \rst - Return the registered type object for a C++ class, given as a template parameter. - py::type() returns the Python type object previously registered for T. -\endrst */ template -handle type() { - static_assert( +type type::of() { + static_assert( std::is_base_of>::value, "This currently only works for registered C++ types. The type here is most likely type converted (using type_caster)." ); - return detail::get_type_handle(typeid(T), true); + return type((PyTypeObject*) detail::get_type_handle(typeid(T), true).ptr()); } -inline handle type(handle h) { - PyObject* obj = (PyObject *) Py_TYPE(h.ptr()); - return handle(obj); -} #define PYBIND11_MAKE_OPAQUE(...) \ namespace pybind11 { namespace detail { \ diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 00c791aada..0e61bc0157 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -19,6 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) /* A few forward declarations */ class handle; class object; class str; class iterator; +class type; struct arg; struct arg_v; PYBIND11_NAMESPACE_BEGIN(detail) @@ -890,6 +891,22 @@ class iterator : public object { object value = {}; }; +class type : public handle { +public: + // Explicit omitted here since PyTypeObject required (rather than just PyObject) + type(PyTypeObject* type_ptr) : handle((PyObject*) type_ptr) {} + + /// Giving a handle/object gets the type from it + explicit type(const handle& h) : handle((PyObject *) Py_TYPE(h.ptr())) {} + + /// Convert C++ type to py::type if prevously registered. Does not convert standard types, like int, float. etc. yet. + template + static type of(); + + /// Custom check function that also ensures this is a type + bool check() const { return ptr() != nullptr && PyType_Check(ptr());} +}; + class iterable : public object { public: PYBIND11_OBJECT_DEFAULT(iterable, object, detail::PyIterable_Check) diff --git a/tests/test_class.cpp b/tests/test_class.cpp index d57a0acb20..c164f441e8 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -140,11 +140,11 @@ TEST_SUBMODULE(class_, m) { m.def("check_type", [](int category) { // Currently not supported (via a fail at compile time) // if (category == 2) - // return py::type(); + // return py::type::of(); if (category == 1) - return py::type(); + return py::type::of(); else - return py::type(); + return py::type::of(); }); m.def("compute_type", [](py::handle h) { From 7548f7a4c6f21ff7af93e23ae4a364b66e820e85 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 11 Sep 2020 14:30:00 -0400 Subject: [PATCH 3/7] refactor: py::object as base --- docs/advanced/classes.rst | 4 ++-- include/pybind11/cast.h | 2 +- include/pybind11/pytypes.h | 13 +++++++------ tests/test_class.cpp | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index 53a805551c..a3d119976c 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -1240,9 +1240,9 @@ You can get the type object from a C++ class that has already been registered us .. code-block:: python - auto T_py = py::type::of(); + py::type T_py = py::type::of(); -You can directly use ``py::type(ob)`` to get the type object from any python +You can directly use ``py::type::of(ob)`` to get the type object from any python object, just like ``type(ob)`` in Python. .. versionadded:: 2.6 diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3b42c15314..b1d87bfe0e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -2209,7 +2209,7 @@ template type type::of() { static_assert( std::is_base_of>::value, - "This currently only works for registered C++ types. The type here is most likely type converted (using type_caster)." + "py::type::of only supports the case where T is a registered C++ types. The type here is most likely type converted (using type_caster)." ); return type((PyTypeObject*) detail::get_type_handle(typeid(T), true).ptr()); diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 0e61bc0157..a644e289e6 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -891,20 +891,21 @@ class iterator : public object { object value = {}; }; -class type : public handle { +class type : public object { public: + PYBIND11_OBJECT_DEFAULT(type, object, PyType_Check); + // Explicit omitted here since PyTypeObject required (rather than just PyObject) - type(PyTypeObject* type_ptr) : handle((PyObject*) type_ptr) {} + type(PyTypeObject* type_ptr) : object((PyObject*) type_ptr, borrowed_t()) {} /// Giving a handle/object gets the type from it - explicit type(const handle& h) : handle((PyObject *) Py_TYPE(h.ptr())) {} + static type of(const handle& h) { + return type(Py_TYPE(h.ptr())); + } /// Convert C++ type to py::type if prevously registered. Does not convert standard types, like int, float. etc. yet. template static type of(); - - /// Custom check function that also ensures this is a type - bool check() const { return ptr() != nullptr && PyType_Check(ptr());} }; class iterable : public object { diff --git a/tests/test_class.cpp b/tests/test_class.cpp index c164f441e8..9bd6d1ee53 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -148,7 +148,7 @@ TEST_SUBMODULE(class_, m) { }); m.def("compute_type", [](py::handle h) { - return py::type(h); + return py::type::of(h); }); // test_mismatched_holder From 3de181deb71ac7fa5460d6b83de013c70659a8ce Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sun, 13 Sep 2020 21:49:49 -0400 Subject: [PATCH 4/7] wip: tigher api --- include/pybind11/cast.h | 4 ++-- include/pybind11/pytypes.h | 13 ++++++++----- tests/test_class.cpp | 10 +++++++++- tests/test_class.py | 24 ++++++++++++++++++++++-- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b1d87bfe0e..5601f2ec83 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -2209,10 +2209,10 @@ template type type::of() { static_assert( std::is_base_of>::value, - "py::type::of only supports the case where T is a registered C++ types. The type here is most likely type converted (using type_caster)." + "py::type::of only supports the case where T is a registered C++ types." ); - return type((PyTypeObject*) detail::get_type_handle(typeid(T), true).ptr()); + return type((PyObject*) detail::get_type_handle(typeid(T), true).ptr(), borrowed_t()); } diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index a644e289e6..11c136f173 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -750,6 +750,10 @@ inline bool PyIterable_Check(PyObject *obj) { } } +inline PyObject* Py_TYPE_Convert(PyObject *obj) { + return (PyObject*) Py_TYPE(obj); +} + inline bool PyNone_Check(PyObject *o) { return o == Py_None; } inline bool PyEllipsis_Check(PyObject *o) { return o == Py_Ellipsis; } @@ -891,16 +895,15 @@ class iterator : public object { object value = {}; }; + + class type : public object { public: - PYBIND11_OBJECT_DEFAULT(type, object, PyType_Check); - - // Explicit omitted here since PyTypeObject required (rather than just PyObject) - type(PyTypeObject* type_ptr) : object((PyObject*) type_ptr, borrowed_t()) {} + PYBIND11_OBJECT_CVT(type, object, PyType_Check, detail::Py_TYPE_Convert); /// Giving a handle/object gets the type from it static type of(const handle& h) { - return type(Py_TYPE(h.ptr())); + return type(detail::Py_TYPE_Convert(h.ptr()), borrowed_t()); } /// Convert C++ type to py::type if prevously registered. Does not convert standard types, like int, float. etc. yet. diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 9bd6d1ee53..ef3f2bb1e0 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -147,10 +147,18 @@ TEST_SUBMODULE(class_, m) { return py::type::of(); }); - m.def("compute_type", [](py::handle h) { + m.def("get_type", [](py::handle h) { return py::type::of(h); }); + m.def("get_type_direct", [](py::object ob) { + return py::type(ob); + }); + + m.def("get_type_implicit", [](py::object ob) -> py::type { + return ob; + }); + // test_mismatched_holder struct MismatchBase1 { }; struct MismatchDerived1 : MismatchBase1 { }; diff --git a/tests/test_class.py b/tests/test_class.py index bd2bf0de61..f6b21587af 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -39,8 +39,28 @@ def test_type(): def test_type_py(): - assert m.compute_type(1) == int - assert m.compute_type(m.DerivedClass1()) == m.DerivedClass1 + assert m.get_type(1) == int + assert m.get_type(m.DerivedClass1()) == m.DerivedClass1 + assert m.get_type(int) == type + + +def test_type_implicit(): + assert m.get_type_implicit(m.DerivedClass1()) == m.DerivedClass1 + assert m.get_type_implicit(1) == int + assert m.get_type_implicit(int) == int + + +def test_type_direct(): + # Comment following line to avoid segfault (getting type of registered + # class twice causes segfault) + assert m.get_type_direct(m.DerivedClass1()) == m.DerivedClass1 + assert m.get_type_direct(1) == int + assert m.get_type_direct(int) == int + + +# Uncomment for segfault +# def test_type_implicit_again(): +# assert m.get_type_implicit(m.DerivedClass1()) == m.DerivedClass1 def test_docstrings(doc): From 38729d0f9bbc8451ac85445e9aa4d22fb62243bd Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 14 Sep 2020 10:17:09 -0400 Subject: [PATCH 5/7] refactor: fix conversion and limit API further --- include/pybind11/pytypes.h | 15 +++++---------- tests/test_class.cpp | 10 +--------- tests/test_class.py | 20 +++----------------- 3 files changed, 9 insertions(+), 36 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 11c136f173..22b98ab891 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -750,10 +750,6 @@ inline bool PyIterable_Check(PyObject *obj) { } } -inline PyObject* Py_TYPE_Convert(PyObject *obj) { - return (PyObject*) Py_TYPE(obj); -} - inline bool PyNone_Check(PyObject *o) { return o == Py_None; } inline bool PyEllipsis_Check(PyObject *o) { return o == Py_Ellipsis; } @@ -899,14 +895,13 @@ class iterator : public object { class type : public object { public: - PYBIND11_OBJECT_CVT(type, object, PyType_Check, detail::Py_TYPE_Convert); + PYBIND11_OBJECT_COMMON(type, object, PyType_Check) - /// Giving a handle/object gets the type from it - static type of(const handle& h) { - return type(detail::Py_TYPE_Convert(h.ptr()), borrowed_t()); - } + explicit type(handle h): type((PyObject*) Py_TYPE(h.ptr()), borrowed_t{}) {} + explicit type(object ob): type((PyObject*) Py_TYPE(ob.ptr()), borrowed_t{}) {} - /// Convert C++ type to py::type if prevously registered. Does not convert standard types, like int, float. etc. yet. + /// Convert C++ type to py::type if previously registered. Does not convert + //standard types, like int, float. etc. yet. template static type of(); }; diff --git a/tests/test_class.cpp b/tests/test_class.cpp index ef3f2bb1e0..3a0ae0acb3 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -147,18 +147,10 @@ TEST_SUBMODULE(class_, m) { return py::type::of(); }); - m.def("get_type", [](py::handle h) { - return py::type::of(h); - }); - - m.def("get_type_direct", [](py::object ob) { + m.def("get_type", [](py::object ob) { return py::type(ob); }); - m.def("get_type_implicit", [](py::object ob) -> py::type { - return ob; - }); - // test_mismatched_holder struct MismatchBase1 { }; struct MismatchDerived1 : MismatchBase1 { }; diff --git a/tests/test_class.py b/tests/test_class.py index f6b21587af..6e36ad458b 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -44,23 +44,9 @@ def test_type_py(): assert m.get_type(int) == type -def test_type_implicit(): - assert m.get_type_implicit(m.DerivedClass1()) == m.DerivedClass1 - assert m.get_type_implicit(1) == int - assert m.get_type_implicit(int) == int - - -def test_type_direct(): - # Comment following line to avoid segfault (getting type of registered - # class twice causes segfault) - assert m.get_type_direct(m.DerivedClass1()) == m.DerivedClass1 - assert m.get_type_direct(1) == int - assert m.get_type_direct(int) == int - - -# Uncomment for segfault -# def test_type_implicit_again(): -# assert m.get_type_implicit(m.DerivedClass1()) == m.DerivedClass1 +def test_type_py_nodelete(): + # If the above test deleted the class, this will segfault + assert m.get_type(m.DerivedClass1()) == m.DerivedClass1 def test_docstrings(doc): From e950722d50e84df8b5be4f4518bbe04080a5b9a5 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 14 Sep 2020 10:49:50 -0400 Subject: [PATCH 6/7] docs: some added notes from @EricCousineau-TRI --- docs/advanced/cast/index.rst | 2 ++ docs/advanced/classes.rst | 4 ++++ include/pybind11/pytypes.h | 3 ++- tests/test_class.cpp | 1 + tests/test_class.py | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/advanced/cast/index.rst b/docs/advanced/cast/index.rst index 724585c920..3ce9ea0286 100644 --- a/docs/advanced/cast/index.rst +++ b/docs/advanced/cast/index.rst @@ -1,3 +1,5 @@ +.. _type-conversions: + Type conversions ################ diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index a3d119976c..baf7e4c8b7 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -1245,4 +1245,8 @@ You can get the type object from a C++ class that has already been registered us You can directly use ``py::type::of(ob)`` to get the type object from any python object, just like ``type(ob)`` in Python. +.. note:: + + Other types, like ``py::type::of``, do not work, see :ref:`type-conversions`. + .. versionadded:: 2.6 diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 22b98ab891..2f4b9c1ff9 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -901,7 +901,8 @@ class type : public object { explicit type(object ob): type((PyObject*) Py_TYPE(ob.ptr()), borrowed_t{}) {} /// Convert C++ type to py::type if previously registered. Does not convert - //standard types, like int, float. etc. yet. + // standard types, like int, float. etc. yet. + // See https://github.com/pybind/pybind11/issues/2486 template static type of(); }; diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 3a0ae0acb3..61084f2f30 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -139,6 +139,7 @@ TEST_SUBMODULE(class_, m) { // test_type m.def("check_type", [](int category) { // Currently not supported (via a fail at compile time) + // See https://github.com/pybind/pybind11/issues/2486 // if (category == 2) // return py::type::of(); if (category == 1) diff --git a/tests/test_class.py b/tests/test_class.py index 6e36ad458b..e833006ab6 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -35,6 +35,7 @@ def test_type(): assert 'Invalid' in str(execinfo.value) # Currently not supported + # See https://github.com/pybind/pybind11/issues/2486 # assert m.check_type(2) == int From 636d5cf64acd693a24c2d1bf1470ce3f83dcdc75 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 14 Sep 2020 12:20:42 -0400 Subject: [PATCH 7/7] refactor: use py::type::of --- docs/advanced/classes.rst | 2 +- include/pybind11/pytypes.h | 5 ++--- tests/test_class.cpp | 12 ++++++++++-- tests/test_class.py | 22 ++++++++++++++++------ 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index baf7e4c8b7..b91e8a1fce 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -1247,6 +1247,6 @@ object, just like ``type(ob)`` in Python. .. note:: - Other types, like ``py::type::of``, do not work, see :ref:`type-conversions`. + Other types, like ``py::type::of()``, do not work, see :ref:`type-conversions`. .. versionadded:: 2.6 diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 2f4b9c1ff9..c1219fc2eb 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -895,10 +895,9 @@ class iterator : public object { class type : public object { public: - PYBIND11_OBJECT_COMMON(type, object, PyType_Check) + PYBIND11_OBJECT(type, object, PyType_Check) - explicit type(handle h): type((PyObject*) Py_TYPE(h.ptr()), borrowed_t{}) {} - explicit type(object ob): type((PyObject*) Py_TYPE(ob.ptr()), borrowed_t{}) {} + static type of(handle h) { return type((PyObject*) Py_TYPE(h.ptr()), borrowed_t{}); } /// Convert C++ type to py::type if previously registered. Does not convert // standard types, like int, float. etc. yet. diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 61084f2f30..b7d52a1b5b 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -148,8 +148,16 @@ TEST_SUBMODULE(class_, m) { return py::type::of(); }); - m.def("get_type", [](py::object ob) { - return py::type(ob); + m.def("get_type_of", [](py::object ob) { + return py::type::of(ob); + }); + + m.def("as_type", [](py::object ob) { + auto tp = py::type(ob); + if (py::isinstance(ob)) + return tp; + else + throw std::runtime_error("Invalid type"); }); // test_mismatched_holder diff --git a/tests/test_class.py b/tests/test_class.py index e833006ab6..be21f3709f 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -39,15 +39,25 @@ def test_type(): # assert m.check_type(2) == int -def test_type_py(): - assert m.get_type(1) == int - assert m.get_type(m.DerivedClass1()) == m.DerivedClass1 - assert m.get_type(int) == type +def test_type_of_py(): + assert m.get_type_of(1) == int + assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1 + assert m.get_type_of(int) == type -def test_type_py_nodelete(): +def test_type_of_py_nodelete(): # If the above test deleted the class, this will segfault - assert m.get_type(m.DerivedClass1()) == m.DerivedClass1 + assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1 + + +def test_as_type_py(): + assert m.as_type(int) == int + + with pytest.raises(RuntimeError): + assert m.as_type(1) == int + + with pytest.raises(RuntimeError): + assert m.as_type(m.DerivedClass1()) == m.DerivedClass1 def test_docstrings(doc):