mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +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;
|
||||
handle builtins(PyEval_GetBuiltins());
|
||||
const char *id = PYBIND11_INTERNALS_ID;
|
||||
capsule caps;
|
||||
if (builtins.contains(id)) {
|
||||
caps = builtins[id];
|
||||
}
|
||||
if (caps.check()) {
|
||||
internals_ptr = caps;
|
||||
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
|
||||
internals_ptr = capsule(builtins[id]);
|
||||
} else {
|
||||
internals_ptr = new internals();
|
||||
#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);
|
||||
}
|
||||
|
||||
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() {
|
||||
if (!PyErr_Occurred()) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown internal error occurred");
|
||||
@ -536,9 +543,8 @@ public:
|
||||
}
|
||||
|
||||
/* Check if this is a capsule */
|
||||
capsule c(h, true);
|
||||
if (c.check()) {
|
||||
value = (void *) c;
|
||||
if (isinstance<capsule>(h)) {
|
||||
value = capsule(h, 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 <typename type>
|
||||
struct type_caster<type, enable_if_t<is_pyobject<type>::value>> {
|
||||
public:
|
||||
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); return value.check(); }
|
||||
struct pyobject_caster {
|
||||
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>
|
||||
bool load(handle src, bool /* convert */) { value = src; return static_cast<bool>(value); }
|
||||
|
||||
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 */) {
|
||||
return src.inc_ref();
|
||||
@ -1000,6 +1010,9 @@ public:
|
||||
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:
|
||||
// At compile time:
|
||||
// - 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) {
|
||||
array_t<Scalar> buf(src, true);
|
||||
if (!buf.check())
|
||||
if (!buf)
|
||||
return false;
|
||||
|
||||
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 nnz = obj.attr("nnz").cast<Index>();
|
||||
|
||||
if (!values.check() || !innerIndices.check() || !outerIndices.check())
|
||||
if (!values || !innerIndices || !outerIndices)
|
||||
return false;
|
||||
|
||||
value = Eigen::MappedSparseMatrix<Scalar, Type::Flags, StorageIndex>(
|
||||
|
@ -324,10 +324,9 @@ public:
|
||||
|
||||
int flags = 0;
|
||||
if (base && ptr) {
|
||||
array base_array(base, true);
|
||||
if (base_array.check())
|
||||
if (isinstance<array>(base))
|
||||
/* 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
|
||||
/* Writable by default, easy to downgrade later on if needed */
|
||||
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)
|
||||
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, 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)
|
||||
class args_proxy;
|
||||
inline bool isinstance_generic(handle obj, const std::type_info &tp);
|
||||
|
||||
// Accessor forward declarations
|
||||
template <typename Policy> class accessor;
|
||||
@ -91,6 +92,7 @@ public:
|
||||
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; }
|
||||
PYBIND11_DEPRECATED("Use handle::operator bool() instead")
|
||||
bool check() const { return m_ptr != nullptr; }
|
||||
protected:
|
||||
PyObject *m_ptr = nullptr;
|
||||
@ -135,6 +137,16 @@ public:
|
||||
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) {
|
||||
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& 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; } \
|
||||
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) \
|
||||
PYBIND11_OBJECT_CVT(Name, Parent, CheckFun, )
|
||||
|
@ -45,9 +45,9 @@ template <typename Type, typename Key> struct set_caster {
|
||||
using key_conv = make_caster<Key>;
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
pybind11::set s(src, true);
|
||||
if (!s.check())
|
||||
if (!isinstance<pybind11::set>(src))
|
||||
return false;
|
||||
pybind11::set s(src, true);
|
||||
value.clear();
|
||||
key_conv conv;
|
||||
for (auto entry : s) {
|
||||
@ -77,9 +77,9 @@ template <typename Type, typename Key, typename Value> struct map_caster {
|
||||
using value_conv = make_caster<Value>;
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
dict d(src, true);
|
||||
if (!d.check())
|
||||
if (!isinstance<dict>(src))
|
||||
return false;
|
||||
dict d(src, true);
|
||||
key_conv kconv;
|
||||
value_conv vconv;
|
||||
value.clear();
|
||||
@ -112,9 +112,9 @@ template <typename Type, typename Value> struct list_caster {
|
||||
using value_conv = make_caster<Value>;
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
sequence s(src, true);
|
||||
if (!s.check())
|
||||
if (!isinstance<sequence>(src))
|
||||
return false;
|
||||
sequence s(src, true);
|
||||
value_conv conv;
|
||||
value.clear();
|
||||
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>;
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
list l(src, true);
|
||||
if (!l.check())
|
||||
if (!isinstance<list>(src))
|
||||
return false;
|
||||
list l(src, true);
|
||||
if (l.size() != Size)
|
||||
return false;
|
||||
value_conv conv;
|
||||
|
@ -83,4 +83,18 @@ test_initializer inheritance([](py::module &m) {
|
||||
return new BaseClass();
|
||||
});
|
||||
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(0)).__name__ == "BaseClass"
|
||||
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