feat: py::type::of<T>() and py::type::of(h) (#2364)

* feat: type<T>()

* refactor: using py::type as class

* refactor: py::object as base

* wip: tigher api

* refactor: fix conversion and limit API further

* docs: some added notes from @EricCousineau-TRI

* refactor: use py::type::of
This commit is contained in:
Henry Schreiner 2020-09-14 18:06:26 -04:00 committed by GitHub
parent 32bb9071aa
commit f12ec00d70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 0 deletions

View File

@ -1,3 +1,5 @@
.. _type-conversions:
Type conversions Type conversions
################ ################

View File

@ -1232,3 +1232,21 @@ appropriate derived-class pointer (e.g. using
more complete example, including a demonstration of how to provide more complete example, including a demonstration of how to provide
automatic downcasting for an entire class hierarchy without automatic downcasting for an entire class hierarchy without
writing one get() function for each class. 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
py::type T_py = py::type::of<T>();
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<int>()``, do not work, see :ref:`type-conversions`.
.. versionadded:: 2.6

View File

@ -2204,6 +2204,18 @@ object object_api<Derived>::call(Args &&...args) const {
PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(detail)
template<typename T>
type type::of() {
static_assert(
std::is_base_of<detail::type_caster_generic, detail::make_caster<T>>::value,
"py::type::of<T> only supports the case where T is a registered C++ types."
);
return type((PyObject*) detail::get_type_handle(typeid(T), true).ptr(), borrowed_t());
}
#define PYBIND11_MAKE_OPAQUE(...) \ #define PYBIND11_MAKE_OPAQUE(...) \
namespace pybind11 { namespace detail { \ namespace pybind11 { namespace detail { \
template<> class type_caster<__VA_ARGS__> : public type_caster_base<__VA_ARGS__> { }; \ template<> class type_caster<__VA_ARGS__> : public type_caster_base<__VA_ARGS__> { }; \

View File

@ -19,6 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/* A few forward declarations */ /* A few forward declarations */
class handle; class object; class handle; class object;
class str; class iterator; class str; class iterator;
class type;
struct arg; struct arg_v; struct arg; struct arg_v;
PYBIND11_NAMESPACE_BEGIN(detail) PYBIND11_NAMESPACE_BEGIN(detail)
@ -890,6 +891,21 @@ private:
object value = {}; object value = {};
}; };
class type : public object {
public:
PYBIND11_OBJECT(type, object, PyType_Check)
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.
// See https://github.com/pybind/pybind11/issues/2486
template<typename T>
static type of();
};
class iterable : public object { class iterable : public object {
public: public:
PYBIND11_OBJECT_DEFAULT(iterable, object, detail::PyIterable_Check) PYBIND11_OBJECT_DEFAULT(iterable, object, detail::PyIterable_Check)

View File

@ -134,6 +134,32 @@ TEST_SUBMODULE(class_, m) {
); );
}); });
struct Invalid {};
// 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<int>();
if (category == 1)
return py::type::of<DerivedClass1>();
else
return py::type::of<Invalid>();
});
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<py::type>(ob))
return tp;
else
throw std::runtime_error("Invalid type");
});
// test_mismatched_holder // test_mismatched_holder
struct MismatchBase1 { }; struct MismatchBase1 { };
struct MismatchDerived1 : MismatchBase1 { }; struct MismatchDerived1 : MismatchBase1 { };

View File

@ -26,6 +26,40 @@ def test_instance(msg):
assert cstats.alive() == 0 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
# See https://github.com/pybind/pybind11/issues/2486
# assert m.check_type(2) == int
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_of_py_nodelete():
# If the above test deleted the class, this will segfault
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): def test_docstrings(doc):
assert doc(UserType) == "A `py::class_` type for testing" assert doc(UserType) == "A `py::class_` type for testing"
assert UserType.__name__ == "UserType" assert UserType.__name__ == "UserType"