mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-29 08:32:02 +00:00
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 }); ```
This commit is contained in:
parent
281ccc692c
commit
b4498ef44d
@ -40,12 +40,8 @@ PYBIND11_NOINLINE inline internals &get_internals() {
|
|||||||
return *internals_ptr;
|
return *internals_ptr;
|
||||||
handle builtins(PyEval_GetBuiltins());
|
handle builtins(PyEval_GetBuiltins());
|
||||||
const char *id = PYBIND11_INTERNALS_ID;
|
const char *id = PYBIND11_INTERNALS_ID;
|
||||||
capsule caps;
|
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
|
||||||
if (builtins.contains(id)) {
|
internals_ptr = capsule(builtins[id]);
|
||||||
caps = builtins[id];
|
|
||||||
}
|
|
||||||
if (caps.check()) {
|
|
||||||
internals_ptr = caps;
|
|
||||||
} else {
|
} else {
|
||||||
internals_ptr = new internals();
|
internals_ptr = new internals();
|
||||||
#if defined(WITH_THREAD)
|
#if defined(WITH_THREAD)
|
||||||
@ -111,6 +107,17 @@ PYBIND11_NOINLINE inline handle get_type_handle(const std::type_info &tp, bool t
|
|||||||
return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
|
return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PYBIND11_NOINLINE inline bool isinstance_generic(handle obj, const std::type_info &tp) {
|
||||||
|
const auto type = detail::get_type_handle(tp, false);
|
||||||
|
if (!type)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const auto result = PyObject_IsInstance(obj.ptr(), type.ptr());
|
||||||
|
if (result == -1)
|
||||||
|
throw error_already_set();
|
||||||
|
return result != 0;
|
||||||
|
}
|
||||||
|
|
||||||
PYBIND11_NOINLINE inline std::string error_string() {
|
PYBIND11_NOINLINE inline std::string error_string() {
|
||||||
if (!PyErr_Occurred()) {
|
if (!PyErr_Occurred()) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Unknown internal error occurred");
|
PyErr_SetString(PyExc_RuntimeError, "Unknown internal error occurred");
|
||||||
@ -536,9 +543,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check if this is a capsule */
|
/* Check if this is a capsule */
|
||||||
capsule c(h, true);
|
if (isinstance<capsule>(h)) {
|
||||||
if (c.check()) {
|
value = capsule(h, true);
|
||||||
value = (void *) c;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,13 +992,17 @@ template <> struct handle_type_name<args> { static PYBIND11_DESCR name() { retur
|
|||||||
template <> struct handle_type_name<kwargs> { static PYBIND11_DESCR name() { return _("**kwargs"); } };
|
template <> struct handle_type_name<kwargs> { static PYBIND11_DESCR name() { return _("**kwargs"); } };
|
||||||
|
|
||||||
template <typename type>
|
template <typename type>
|
||||||
struct type_caster<type, enable_if_t<is_pyobject<type>::value>> {
|
struct pyobject_caster {
|
||||||
public:
|
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>
|
||||||
template <typename T = type, enable_if_t<!std::is_base_of<object, T>::value, int> = 0>
|
bool load(handle src, bool /* convert */) { value = src; return static_cast<bool>(value); }
|
||||||
bool load(handle src, bool /* convert */) { value = type(src); return value.check(); }
|
|
||||||
|
|
||||||
template <typename T = type, enable_if_t<std::is_base_of<object, T>::value, int> = 0>
|
template <typename T = type, enable_if_t<std::is_base_of<object, T>::value, int> = 0>
|
||||||
bool load(handle src, bool /* convert */) { value = type(src, true); return value.check(); }
|
bool load(handle src, bool /* convert */) {
|
||||||
|
if (!isinstance<type>(src))
|
||||||
|
return false;
|
||||||
|
value = type(src, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) {
|
static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) {
|
||||||
return src.inc_ref();
|
return src.inc_ref();
|
||||||
@ -1000,6 +1010,9 @@ public:
|
|||||||
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name());
|
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class type_caster<T, enable_if_t<is_pyobject<T>::value>> : public pyobject_caster<T> { };
|
||||||
|
|
||||||
// Our conditions for enabling moving are quite restrictive:
|
// Our conditions for enabling moving are quite restrictive:
|
||||||
// At compile time:
|
// At compile time:
|
||||||
// - T needs to be a non-const, non-pointer, non-reference type
|
// - T needs to be a non-const, non-pointer, non-reference type
|
||||||
|
@ -55,7 +55,7 @@ struct type_caster<Type, enable_if_t<is_eigen_dense<Type>::value && !is_eigen_re
|
|||||||
|
|
||||||
bool load(handle src, bool) {
|
bool load(handle src, bool) {
|
||||||
array_t<Scalar> buf(src, true);
|
array_t<Scalar> buf(src, true);
|
||||||
if (!buf.check())
|
if (!buf)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (buf.ndim() == 1) {
|
if (buf.ndim() == 1) {
|
||||||
@ -201,7 +201,7 @@ struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
|
|||||||
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
|
auto shape = pybind11::tuple((pybind11::object) obj.attr("shape"));
|
||||||
auto nnz = obj.attr("nnz").cast<Index>();
|
auto nnz = obj.attr("nnz").cast<Index>();
|
||||||
|
|
||||||
if (!values.check() || !innerIndices.check() || !outerIndices.check())
|
if (!values || !innerIndices || !outerIndices)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
value = Eigen::MappedSparseMatrix<Scalar, Type::Flags, StorageIndex>(
|
value = Eigen::MappedSparseMatrix<Scalar, Type::Flags, StorageIndex>(
|
||||||
|
@ -324,10 +324,9 @@ public:
|
|||||||
|
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
if (base && ptr) {
|
if (base && ptr) {
|
||||||
array base_array(base, true);
|
if (isinstance<array>(base))
|
||||||
if (base_array.check())
|
|
||||||
/* Copy flags from base (except baseship bit) */
|
/* Copy flags from base (except baseship bit) */
|
||||||
flags = base_array.flags() & ~detail::npy_api::NPY_ARRAY_OWNDATA_;
|
flags = array(base, true).flags() & ~detail::npy_api::NPY_ARRAY_OWNDATA_;
|
||||||
else
|
else
|
||||||
/* Writable by default, easy to downgrade later on if needed */
|
/* Writable by default, easy to downgrade later on if needed */
|
||||||
flags = detail::npy_api::NPY_ARRAY_WRITEABLE_;
|
flags = detail::npy_api::NPY_ARRAY_WRITEABLE_;
|
||||||
@ -627,6 +626,21 @@ struct format_descriptor<T, detail::enable_if_t<std::is_enum<T>::value>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
NAMESPACE_BEGIN(detail)
|
NAMESPACE_BEGIN(detail)
|
||||||
|
template <typename T, int ExtraFlags>
|
||||||
|
struct pyobject_caster<array_t<T, ExtraFlags>> {
|
||||||
|
using type = array_t<T, ExtraFlags>;
|
||||||
|
|
||||||
|
bool load(handle src, bool /* convert */) {
|
||||||
|
value = type(src, true);
|
||||||
|
return static_cast<bool>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static handle cast(const handle &src, return_value_policy /* policy */, handle /* parent */) {
|
||||||
|
return src.inc_ref();
|
||||||
|
}
|
||||||
|
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name());
|
||||||
|
};
|
||||||
|
|
||||||
template <typename T> struct is_std_array : std::false_type { };
|
template <typename T> struct is_std_array : std::false_type { };
|
||||||
template <typename T, size_t N> struct is_std_array<std::array<T, N>> : std::true_type { };
|
template <typename T, size_t N> struct is_std_array<std::array<T, N>> : std::true_type { };
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ struct arg; struct arg_v;
|
|||||||
|
|
||||||
NAMESPACE_BEGIN(detail)
|
NAMESPACE_BEGIN(detail)
|
||||||
class args_proxy;
|
class args_proxy;
|
||||||
|
inline bool isinstance_generic(handle obj, const std::type_info &tp);
|
||||||
|
|
||||||
// Accessor forward declarations
|
// Accessor forward declarations
|
||||||
template <typename Policy> class accessor;
|
template <typename Policy> class accessor;
|
||||||
@ -91,6 +92,7 @@ public:
|
|||||||
explicit operator bool() const { return m_ptr != nullptr; }
|
explicit operator bool() const { return m_ptr != nullptr; }
|
||||||
bool operator==(const handle &h) const { return m_ptr == h.m_ptr; }
|
bool operator==(const handle &h) const { return m_ptr == h.m_ptr; }
|
||||||
bool operator!=(const handle &h) const { return m_ptr != h.m_ptr; }
|
bool operator!=(const handle &h) const { return m_ptr != h.m_ptr; }
|
||||||
|
PYBIND11_DEPRECATED("Use handle::operator bool() instead")
|
||||||
bool check() const { return m_ptr != nullptr; }
|
bool check() const { return m_ptr != nullptr; }
|
||||||
protected:
|
protected:
|
||||||
PyObject *m_ptr = nullptr;
|
PyObject *m_ptr = nullptr;
|
||||||
@ -135,6 +137,16 @@ public:
|
|||||||
template <typename T> T cast() &&;
|
template <typename T> T cast() &&;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Check if `obj` is an instance of type `T`
|
||||||
|
template <typename T, detail::enable_if_t<std::is_base_of<object, T>::value, int> = 0>
|
||||||
|
bool isinstance(handle obj) { return T::_check(obj); }
|
||||||
|
|
||||||
|
template <typename T, detail::enable_if_t<!std::is_base_of<object, T>::value, int> = 0>
|
||||||
|
bool isinstance(handle obj) { return detail::isinstance_generic(obj, typeid(T)); }
|
||||||
|
|
||||||
|
template <> inline bool isinstance<handle>(handle obj) = delete;
|
||||||
|
template <> inline bool isinstance<object>(handle obj) { return obj.ptr() != nullptr; }
|
||||||
|
|
||||||
inline bool hasattr(handle obj, handle name) {
|
inline bool hasattr(handle obj, handle name) {
|
||||||
return PyObject_HasAttr(obj.ptr(), name.ptr()) == 1;
|
return PyObject_HasAttr(obj.ptr(), name.ptr()) == 1;
|
||||||
}
|
}
|
||||||
@ -386,7 +398,9 @@ NAMESPACE_END(detail)
|
|||||||
Name(object&& o) noexcept : Parent(std::move(o)) { CvtStmt; } \
|
Name(object&& o) noexcept : Parent(std::move(o)) { CvtStmt; } \
|
||||||
Name& operator=(object&& o) noexcept { (void) object::operator=(std::move(o)); CvtStmt; return *this; } \
|
Name& operator=(object&& o) noexcept { (void) object::operator=(std::move(o)); CvtStmt; return *this; } \
|
||||||
Name& operator=(const object& o) { return static_cast<Name&>(object::operator=(o)); CvtStmt; } \
|
Name& operator=(const object& o) { return static_cast<Name&>(object::operator=(o)); CvtStmt; } \
|
||||||
bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); }
|
PYBIND11_DEPRECATED("Use py::isinstance<py::python_type>(obj) instead") \
|
||||||
|
bool check() const { return m_ptr != nullptr && (bool) CheckFun(m_ptr); } \
|
||||||
|
static bool _check(handle h) { return h.ptr() != nullptr && CheckFun(h.ptr()); }
|
||||||
|
|
||||||
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
|
#define PYBIND11_OBJECT(Name, Parent, CheckFun) \
|
||||||
PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, )
|
PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, )
|
||||||
|
@ -45,9 +45,9 @@ template <typename Type, typename Key> struct set_caster {
|
|||||||
using key_conv = make_caster<Key>;
|
using key_conv = make_caster<Key>;
|
||||||
|
|
||||||
bool load(handle src, bool convert) {
|
bool load(handle src, bool convert) {
|
||||||
pybind11::set s(src, true);
|
if (!isinstance<pybind11::set>(src))
|
||||||
if (!s.check())
|
|
||||||
return false;
|
return false;
|
||||||
|
pybind11::set s(src, true);
|
||||||
value.clear();
|
value.clear();
|
||||||
key_conv conv;
|
key_conv conv;
|
||||||
for (auto entry : s) {
|
for (auto entry : s) {
|
||||||
@ -77,9 +77,9 @@ template <typename Type, typename Key, typename Value> struct map_caster {
|
|||||||
using value_conv = make_caster<Value>;
|
using value_conv = make_caster<Value>;
|
||||||
|
|
||||||
bool load(handle src, bool convert) {
|
bool load(handle src, bool convert) {
|
||||||
dict d(src, true);
|
if (!isinstance<dict>(src))
|
||||||
if (!d.check())
|
|
||||||
return false;
|
return false;
|
||||||
|
dict d(src, true);
|
||||||
key_conv kconv;
|
key_conv kconv;
|
||||||
value_conv vconv;
|
value_conv vconv;
|
||||||
value.clear();
|
value.clear();
|
||||||
@ -112,9 +112,9 @@ template <typename Type, typename Value> struct list_caster {
|
|||||||
using value_conv = make_caster<Value>;
|
using value_conv = make_caster<Value>;
|
||||||
|
|
||||||
bool load(handle src, bool convert) {
|
bool load(handle src, bool convert) {
|
||||||
sequence s(src, true);
|
if (!isinstance<sequence>(src))
|
||||||
if (!s.check())
|
|
||||||
return false;
|
return false;
|
||||||
|
sequence s(src, true);
|
||||||
value_conv conv;
|
value_conv conv;
|
||||||
value.clear();
|
value.clear();
|
||||||
reserve_maybe(s, &value);
|
reserve_maybe(s, &value);
|
||||||
@ -157,9 +157,9 @@ template <typename Type, size_t Size> struct type_caster<std::array<Type, Size>>
|
|||||||
using value_conv = make_caster<Type>;
|
using value_conv = make_caster<Type>;
|
||||||
|
|
||||||
bool load(handle src, bool convert) {
|
bool load(handle src, bool convert) {
|
||||||
list l(src, true);
|
if (!isinstance<list>(src))
|
||||||
if (!l.check())
|
|
||||||
return false;
|
return false;
|
||||||
|
list l(src, true);
|
||||||
if (l.size() != Size)
|
if (l.size() != Size)
|
||||||
return false;
|
return false;
|
||||||
value_conv conv;
|
value_conv conv;
|
||||||
|
@ -83,4 +83,18 @@ test_initializer inheritance([](py::module &m) {
|
|||||||
return new BaseClass();
|
return new BaseClass();
|
||||||
});
|
});
|
||||||
m.def("return_none", []() -> BaseClass* { return nullptr; });
|
m.def("return_none", []() -> BaseClass* { return nullptr; });
|
||||||
|
|
||||||
|
m.def("test_isinstance", [](py::list l) {
|
||||||
|
struct Unregistered { }; // checks missing type_info code path
|
||||||
|
|
||||||
|
return py::make_tuple(
|
||||||
|
py::isinstance<py::tuple>(l[0]),
|
||||||
|
py::isinstance<py::dict>(l[1]),
|
||||||
|
py::isinstance<Pet>(l[2]),
|
||||||
|
py::isinstance<Pet>(l[3]),
|
||||||
|
py::isinstance<Dog>(l[4]),
|
||||||
|
py::isinstance<Rabbit>(l[5]),
|
||||||
|
py::isinstance<Unregistered>(l[6])
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -45,3 +45,11 @@ def test_automatic_upcasting():
|
|||||||
assert type(return_class_n(2)).__name__ == "DerivedClass2"
|
assert type(return_class_n(2)).__name__ == "DerivedClass2"
|
||||||
assert type(return_class_n(0)).__name__ == "BaseClass"
|
assert type(return_class_n(0)).__name__ == "BaseClass"
|
||||||
assert type(return_class_n(1)).__name__ == "DerivedClass1"
|
assert type(return_class_n(1)).__name__ == "DerivedClass1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_isinstance():
|
||||||
|
from pybind11_tests import test_isinstance, Pet, Dog
|
||||||
|
|
||||||
|
objects = [tuple(), dict(), Pet("Polly", "parrot")] + [Dog("Molly")] * 4
|
||||||
|
expected = (True, True, True, True, True, False, False)
|
||||||
|
assert test_isinstance(objects) == expected
|
||||||
|
Loading…
Reference in New Issue
Block a user