Skip to content

Commit b4498ef

Browse files
dean0x7dwjakob
authored andcommitted
Add py::isinstance<T>(obj) for generalized Python type checking
Allows checking the Python types before creating an object instead of after. For example: ```c++ auto l = list(ptr, true); if (l.check()) // ... ``` The above is replaced with: ```c++ if (isinstance<list>(ptr)) { auto l = reinterpret_borrow(ptr); // ... } ``` This deprecates `py::object::check()`. `py::isinstance()` covers the same use case, but it can also check for user-defined types: ```c++ class Pet { ... }; py::class_<Pet>(...); m.def("is_pet", [](py::object obj) { return py::isinstance<Pet>(obj); // works as expected }); ```
1 parent 281ccc6 commit b4498ef

File tree

7 files changed

+91
-28
lines changed

7 files changed

+91
-28
lines changed

include/pybind11/cast.h

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,8 @@ PYBIND11_NOINLINE inline internals &get_internals() {
4040
return *internals_ptr;
4141
handle builtins(PyEval_GetBuiltins());
4242
const char *id = PYBIND11_INTERNALS_ID;
43-
capsule caps;
44-
if (builtins.contains(id)) {
45-
caps = builtins[id];
46-
}
47-
if (caps.check()) {
48-
internals_ptr = caps;
43+
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
44+
internals_ptr = capsule(builtins[id]);
4945
} else {
5046
internals_ptr = new internals();
5147
#if defined(WITH_THREAD)
@@ -111,6 +107,17 @@ PYBIND11_NOINLINE inline handle get_type_handle(const std::type_info &tp, bool t
111107
return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
112108
}
113109

110+
PYBIND11_NOINLINE inline bool isinstance_generic(handle obj, const std::type_info &tp) {
111+
const auto type = detail::get_type_handle(tp, false);
112+
if (!type)
113+
return false;
114+
115+
const auto result = PyObject_IsInstance(obj.ptr(), type.ptr());
116+
if (result == -1)
117+
throw error_already_set();
118+
return result != 0;
119+
}
120+
114121
PYBIND11_NOINLINE inline std::string error_string() {
115122
if (!PyErr_Occurred()) {
116123
PyErr_SetString(PyExc_RuntimeError, "Unknown internal error occurred");
@@ -536,9 +543,8 @@ template <> class type_caster<void> : public type_caster<void_type> {
536543
}
537544

538545
/* Check if this is a capsule */
539-
capsule c(h, true);
540-
if (c.check()) {
541-
value = (void *) c;
546+
if (isinstance<capsule>(h)) {
547+
value = capsule(h, true);
542548
return true;
543549
}
544550

@@ -986,20 +992,27 @@ template <> struct handle_type_name<args> { static PYBIND11_DESCR name() { retur
986992
template <> struct handle_type_name<kwargs> { static PYBIND11_DESCR name() { return _("**kwargs"); } };
987993

988994
template <typename type>
989-
struct type_caster<type, enable_if_t<is_pyobject<type>::value>> {
990-
public:
991-
template <typename T = type, enable_if_t<!std::is_base_of<object, T>::value, int> = 0>
992-
bool load(handle src, bool /* convert */) { value = type(src); return value.check(); }
995+
struct pyobject_caster {
996+
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>
997+
bool load(handle src, bool /* convert */) { value = src; return static_cast<bool>(value); }
993998

994999
template <typename T = type, enable_if_t<std::is_base_of<object, T>::value, int> = 0>
995-
bool load(handle src, bool /* convert */) { value = type(src, true); return value.check(); }
1000+
bool load(handle src, bool /* convert */) {
1001+
if (!isinstance<type>(src))
1002+
return false;
1003+
value = type(src, true);
1004+
return true;
1005+
}
9961006

9971007
static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) {
9981008
return src.inc_ref();
9991009
}
10001010
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name());
10011011
};
10021012

1013+
template <typename T>
1014+
class type_caster<T, enable_if_t<is_pyobject<T>::value>> : public pyobject_caster<T> { };
1015+
10031016
// Our conditions for enabling moving are quite restrictive:
10041017
// At compile time:
10051018
// - T needs to be a non-const, non-pointer, non-reference type

include/pybind11/eigen.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ struct type_caster<Type, enable_if_t<is_eigen_dense<Type>::value && !is_eigen_re
5555

5656
bool load(handle src, bool) {
5757
array_t<Scalar> buf(src, true);
58-
if (!buf.check())
58+
if (!buf)
5959
return false;
6060

6161
if (buf.ndim() == 1) {
@@ -201,7 +201,7 @@ struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
201201
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
202202
auto nnz = obj.attr("nnz").cast<Index>();
203203

204-
if (!values.check() || !innerIndices.check() || !outerIndices.check())
204+
if (!values || !innerIndices || !outerIndices)
205205
return false;
206206

207207
value = Eigen::MappedSparseMatrix<Scalar, Type::Flags, StorageIndex>(

include/pybind11/numpy.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,9 @@ class array : public buffer {
324324

325325
int flags = 0;
326326
if (base && ptr) {
327-
array base_array(base, true);
328-
if (base_array.check())
327+
if (isinstance<array>(base))
329328
/* Copy flags from base (except baseship bit) */
330-
flags = base_array.flags() & ~detail::npy_api::NPY_ARRAY_OWNDATA_;
329+
flags = array(base, true).flags() & ~detail::npy_api::NPY_ARRAY_OWNDATA_;
331330
else
332331
/* Writable by default, easy to downgrade later on if needed */
333332
flags = detail::npy_api::NPY_ARRAY_WRITEABLE_;
@@ -627,6 +626,21 @@ struct format_descriptor<T, detail::enable_if_t<std::is_enum<T>::value>> {
627626
};
628627

629628
NAMESPACE_BEGIN(detail)
629+
template <typename T, int ExtraFlags>
630+
struct pyobject_caster<array_t<T, ExtraFlags>> {
631+
using type = array_t<T, ExtraFlags>;
632+
633+
bool load(handle src, bool /* convert */) {
634+
value = type(src, true);
635+
return static_cast<bool>(value);
636+
}
637+
638+
static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) {
639+
return src.inc_ref();
640+
}
641+
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name());
642+
};
643+
630644
template <typename T> struct is_std_array : std::false_type { };
631645
template <typename T, size_t N> struct is_std_array<std::array<T, N>> : std::true_type { };
632646

include/pybind11/pytypes.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct arg; struct arg_v;
2222

2323
NAMESPACE_BEGIN(detail)
2424
class args_proxy;
25+
inline bool isinstance_generic(handle obj, const std::type_info &tp);
2526

2627
// Accessor forward declarations
2728
template <typename Policy> class accessor;
@@ -91,6 +92,7 @@ class handle : public detail::object_api<handle> {
9192
explicit operator bool() const { return m_ptr != nullptr; }
9293
bool operator==(const handle &h) const { return m_ptr == h.m_ptr; }
9394
bool operator!=(const handle &h) const { return m_ptr != h.m_ptr; }
95+
PYBIND11_DEPRECATED("Use handle::operator bool() instead")
9496
bool check() const { return m_ptr != nullptr; }
9597
protected:
9698
PyObject *m_ptr = nullptr;
@@ -135,6 +137,16 @@ class object : public handle {
135137
template <typename T> T cast() &&;
136138
};
137139

140+
/// Check if `obj` is an instance of type `T`
141+
template <typename T, detail::enable_if_t<std::is_base_of<object, T>::value, int> = 0>
142+
bool isinstance(handle obj) { return T::_check(obj); }
143+
144+
template <typename T, detail::enable_if_t<!std::is_base_of<object, T>::value, int> = 0>
145+
bool isinstance(handle obj) { return detail::isinstance_generic(obj, typeid(T)); }
146+
147+
template <> inline bool isinstance<handle>(handle obj) = delete;
148+
template <> inline bool isinstance<object>(handle obj) { return obj.ptr() != nullptr; }
149+
138150
inline bool hasattr(handle obj, handle name) {
139151
return PyObject_HasAttr(obj.ptr(), name.ptr()) == 1;
140152
}
@@ -386,7 +398,9 @@ NAMESPACE_END(detail)
386398
Name(object&& o) noexcept : Parent(std::move(o)) { CvtStmt; } \
387399
Name& operator=(object&& o) noexcept { (void) object::operator=(std::move(o)); CvtStmt; return *this; } \
388400
Name& operator=(const object& o) { return static_cast<Name&>(object::operator=(o)); CvtStmt; } \
389-
bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); }
401+
PYBIND11_DEPRECATED("Use py::isinstance<py::python_type>(obj) instead") \
402+
bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); } \
403+
static bool _check(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); }
390404

391405
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
392406
PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, )

include/pybind11/stl.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ template <typename Type, typename Key> struct set_caster {
4545
using key_conv = make_caster<Key>;
4646

4747
bool load(handle src, bool convert) {
48-
pybind11::set s(src, true);
49-
if (!s.check())
48+
if (!isinstance<pybind11::set>(src))
5049
return false;
50+
pybind11::set s(src, true);
5151
value.clear();
5252
key_conv conv;
5353
for (auto entry : s) {
@@ -77,9 +77,9 @@ template <typename Type, typename Key, typename Value> struct map_caster {
7777
using value_conv = make_caster<Value>;
7878

7979
bool load(handle src, bool convert) {
80-
dict d(src, true);
81-
if (!d.check())
80+
if (!isinstance<dict>(src))
8281
return false;
82+
dict d(src, true);
8383
key_conv kconv;
8484
value_conv vconv;
8585
value.clear();
@@ -112,9 +112,9 @@ template <typename Type, typename Value> struct list_caster {
112112
using value_conv = make_caster<Value>;
113113

114114
bool load(handle src, bool convert) {
115-
sequence s(src, true);
116-
if (!s.check())
115+
if (!isinstance<sequence>(src))
117116
return false;
117+
sequence s(src, true);
118118
value_conv conv;
119119
value.clear();
120120
reserve_maybe(s, &value);
@@ -157,9 +157,9 @@ template <typename Type, size_t Size> struct type_caster<std::array<Type, Size>>
157157
using value_conv = make_caster<Type>;
158158

159159
bool load(handle src, bool convert) {
160-
list l(src, true);
161-
if (!l.check())
160+
if (!isinstance<list>(src))
162161
return false;
162+
list l(src, true);
163163
if (l.size() != Size)
164164
return false;
165165
value_conv conv;

tests/test_inheritance.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,18 @@ test_initializer inheritance([](py::module &m) {
8383
return new BaseClass();
8484
});
8585
m.def("return_none", []() -> BaseClass* { return nullptr; });
86+
87+
m.def("test_isinstance", [](py::list l) {
88+
struct Unregistered { }; // checks missing type_info code path
89+
90+
return py::make_tuple(
91+
py::isinstance<py::tuple>(l[0]),
92+
py::isinstance<py::dict>(l[1]),
93+
py::isinstance<Pet>(l[2]),
94+
py::isinstance<Pet>(l[3]),
95+
py::isinstance<Dog>(l[4]),
96+
py::isinstance<Rabbit>(l[5]),
97+
py::isinstance<Unregistered>(l[6])
98+
);
99+
});
86100
});

tests/test_inheritance.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,11 @@ def test_automatic_upcasting():
4545
assert type(return_class_n(2)).__name__ == "DerivedClass2"
4646
assert type(return_class_n(0)).__name__ == "BaseClass"
4747
assert type(return_class_n(1)).__name__ == "DerivedClass1"
48+
49+
50+
def test_isinstance():
51+
from pybind11_tests import test_isinstance, Pet, Dog
52+
53+
objects = [tuple(), dict(), Pet("Polly", "parrot")] + [Dog("Molly")] * 4
54+
expected = (True, True, True, True, True, False, False)
55+
assert test_isinstance(objects) == expected

0 commit comments

Comments
 (0)