Commit f12ec00d by Henry Schreiner Committed by GitHub

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
parent 32bb9071
.. _type-conversions:
Type conversions Type conversions
################ ################
......
...@@ -1232,3 +1232,21 @@ appropriate derived-class pointer (e.g. using ...@@ -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
...@@ -2204,6 +2204,18 @@ object object_api<Derived>::call(Args &&...args) const { ...@@ -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__> { }; \
......
...@@ -19,6 +19,7 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) ...@@ -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: ...@@ -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)
......
...@@ -134,6 +134,32 @@ TEST_SUBMODULE(class_, m) { ...@@ -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 { };
......
...@@ -26,6 +26,40 @@ def test_instance(msg): ...@@ -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"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment