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:
Dean Moldovan 2016-10-23 14:50:08 +02:00 committed by Wenzel Jakob
parent 281ccc692c
commit b4498ef44d
7 changed files with 91 additions and 28 deletions

View File

@ -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

View File

@ -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>(

View File

@ -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 { };

View File

@ -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, )

View File

@ -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;

View File

@ -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])
);
});
}); });

View File

@ -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