Make attr and item accessors throw on error instead of returning nullptr
This also adds the `hasattr` and `getattr` functions which are needed
with the new attribute behavior. The new functions behave exactly like
their Python counterparts.
Similarly `object` gets a `contains` method which calls `__contains__`,
i.e. it's the same as the `in` keyword in Python.
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index 1d9740d..7fa0348 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -39,7 +39,10 @@
return *internals_ptr;
handle builtins(PyEval_GetBuiltins());
const char *id = PYBIND11_INTERNALS_ID;
- capsule caps(builtins[id]);
+ capsule caps;
+ if (builtins.contains(id)) {
+ caps = builtins[id];
+ }
if (caps.check()) {
internals_ptr = caps;
} else {
@@ -1221,7 +1224,7 @@
}
void process(list &/*args_list*/, arg_v a) {
- if (m_kwargs[a.name]) {
+ if (m_kwargs.contains(a.name)) {
#if defined(NDEBUG)
multiple_values_error();
#else
@@ -1240,7 +1243,7 @@
void process(list &/*args_list*/, detail::kwargs_proxy kp) {
for (const auto &k : dict(kp, true)) {
- if (m_kwargs[k.first]) {
+ if (m_kwargs.contains(k.first)) {
#if defined(NDEBUG)
multiple_values_error();
#else
diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h
index a3e6672..fb8d3c6 100644
--- a/include/pybind11/numpy.h
+++ b/include/pybind11/numpy.h
@@ -125,11 +125,11 @@
static npy_api lookup() {
module m = module::import("numpy.core.multiarray");
- object c = (object) m.attr("_ARRAY_API");
+ auto c = m.attr("_ARRAY_API").cast<object>();
#if PY_MAJOR_VERSION >= 3
- void **api_ptr = (void **) (c ? PyCapsule_GetPointer(c.ptr(), NULL) : nullptr);
+ void **api_ptr = (void **) PyCapsule_GetPointer(c.ptr(), NULL);
#else
- void **api_ptr = (void **) (c ? PyCObject_AsVoidPtr(c.ptr()) : nullptr);
+ void **api_ptr = (void **) PyCObject_AsVoidPtr(c.ptr());
#endif
npy_api api;
#define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func];
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 4a6fa8d..a0e2e72 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -278,9 +278,11 @@
object scope_module;
if (rec->scope) {
- scope_module = (object) rec->scope.attr("__module__");
- if (!scope_module)
- scope_module = (object) rec->scope.attr("__name__");
+ if (hasattr(rec->scope, "__module__")) {
+ scope_module = rec->scope.attr("__module__");
+ } else if (hasattr(rec->scope, "__name__")) {
+ scope_module = rec->scope.attr("__name__");
+ }
}
m_ptr = PyCFunction_NewEx(rec->def, rec_capsule.ptr(), scope_module.ptr());
@@ -544,8 +546,8 @@
template <typename Func, typename... Extra>
module &def(const char *name_, Func &&f, const Extra& ... extra) {
- cpp_function func(std::forward<Func>(f), name(name_),
- sibling((handle) attr(name_)), scope(*this), extra...);
+ cpp_function func(std::forward<Func>(f), name(name_), scope(*this),
+ sibling(getattr(*this, name_, none())), extra...);
/* PyModule_AddObject steals a reference to 'func' */
PyModule_AddObject(ptr(), name_, func.inc_ref().ptr());
return *this;
@@ -588,16 +590,18 @@
object name(PYBIND11_FROM_STRING(rec->name), false);
object scope_module;
if (rec->scope) {
- scope_module = (object) rec->scope.attr("__module__");
- if (!scope_module)
- scope_module = (object) rec->scope.attr("__name__");
+ if (hasattr(rec->scope, "__module__")) {
+ scope_module = rec->scope.attr("__module__");
+ } else if (hasattr(rec->scope, "__name__")) {
+ scope_module = rec->scope.attr("__name__");
+ }
}
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
/* Qualified names for Python >= 3.3 */
object scope_qualname;
- if (rec->scope)
- scope_qualname = (object) rec->scope.attr("__qualname__");
+ if (rec->scope && hasattr(rec->scope, "__qualname__"))
+ scope_qualname = rec->scope.attr("__qualname__");
object ht_qualname;
if (scope_qualname) {
ht_qualname = object(PyUnicode_FromFormat(
@@ -894,17 +898,16 @@
template <typename Func, typename... Extra>
class_ &def(const char *name_, Func&& f, const Extra&... extra) {
- cpp_function cf(std::forward<Func>(f), name(name_),
- sibling(attr(name_)), is_method(*this),
- extra...);
+ cpp_function cf(std::forward<Func>(f), name(name_), is_method(*this),
+ sibling(getattr(*this, name_, none())), extra...);
attr(cf.name()) = cf;
return *this;
}
template <typename Func, typename... Extra> class_ &
def_static(const char *name_, Func f, const Extra&... extra) {
- cpp_function cf(std::forward<Func>(f), name(name_),
- sibling(attr(name_)), scope(*this), extra...);
+ cpp_function cf(std::forward<Func>(f), name(name_), scope(*this),
+ sibling(getattr(*this, name_, none())), extra...);
attr(cf.name()) = cf;
return *this;
}
@@ -1338,16 +1341,16 @@
for (size_t i = 0; i < args.size(); ++i) {
strings[i] = args[i].cast<object>().str();
}
- auto sep = kwargs["sep"] ? kwargs["sep"] : cast(" ");
+ auto sep = kwargs.contains("sep") ? kwargs["sep"] : cast(" ");
auto line = sep.attr("join").cast<object>()(strings);
- auto file = kwargs["file"] ? kwargs["file"].cast<object>()
- : module::import("sys").attr("stdout");
+ auto file = kwargs.contains("file") ? kwargs["file"].cast<object>()
+ : module::import("sys").attr("stdout");
auto write = file.attr("write").cast<object>();
write(line);
- write(kwargs["end"] ? kwargs["end"] : cast("\n"));
+ write(kwargs.contains("end") ? kwargs["end"] : cast("\n"));
- if (kwargs["flush"] && kwargs["flush"].cast<bool>()) {
+ if (kwargs.contains("flush") && kwargs["flush"].cast<bool>()) {
file.attr("flush").cast<object>()();
}
}
@@ -1500,7 +1503,7 @@
if (cache.find(key) != cache.end())
return function();
- function overload = (function) py_object.attr(name);
+ function overload = getattr(py_object, name, function());
if (overload.is_cpp_function()) {
cache.insert(key);
return function();
diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h
index 872c90c..180bcdd 100644
--- a/include/pybind11/pytypes.h
+++ b/include/pybind11/pytypes.h
@@ -41,6 +41,7 @@
accessor attr(handle key) const;
accessor attr(const char *key) const;
args_proxy operator*() const;
+ template <typename T> bool contains(T &&key) const;
template <return_value_policy policy = return_value_policy::automatic_reference, typename... Args>
object operator()(Args &&...args) const;
@@ -117,6 +118,52 @@
template <typename T> T cast() &&;
};
+inline bool hasattr(handle obj, handle name) {
+ return PyObject_HasAttr(obj.ptr(), name.ptr()) == 1;
+}
+
+inline bool hasattr(handle obj, const char *name) {
+ return PyObject_HasAttrString(obj.ptr(), name) == 1;
+}
+
+inline object getattr(handle obj, handle name) {
+ PyObject *result = PyObject_GetAttr(obj.ptr(), name.ptr());
+ if (!result) { throw error_already_set(); }
+ return {result, false};
+}
+
+inline object getattr(handle obj, const char *name) {
+ PyObject *result = PyObject_GetAttrString(obj.ptr(), name);
+ if (!result) { throw error_already_set(); }
+ return {result, false};
+}
+
+inline object getattr(handle obj, handle name, handle default_) {
+ if (PyObject *result = PyObject_GetAttr(obj.ptr(), name.ptr())) {
+ return {result, false};
+ } else {
+ PyErr_Clear();
+ return {default_, true};
+ }
+}
+
+inline object getattr(handle obj, const char *name, handle default_) {
+ if (PyObject *result = PyObject_GetAttrString(obj.ptr(), name)) {
+ return {result, false};
+ } else {
+ PyErr_Clear();
+ return {default_, true};
+ }
+}
+
+inline void setattr(handle obj, handle name, handle value) {
+ if (PyObject_SetAttr(obj.ptr(), name.ptr(), value.ptr()) != 0) { throw error_already_set(); }
+}
+
+inline void setattr(handle obj, const char *name, handle value) {
+ if (PyObject_SetAttrString(obj.ptr(), name, value.ptr()) != 0) { throw error_already_set(); }
+}
+
NAMESPACE_BEGIN(detail)
inline handle get_function(handle value) {
if (value) {
@@ -151,24 +198,14 @@
}
operator object() const {
- object result(attr ? PyObject_GetAttr(obj.ptr(), key.ptr())
- : PyObject_GetItem(obj.ptr(), key.ptr()), false);
- if (!result) {PyErr_Clear(); }
- return result;
+ PyObject *result = attr ? PyObject_GetAttr(obj.ptr(), key.ptr())
+ : PyObject_GetItem(obj.ptr(), key.ptr());
+ if (!result) { throw error_already_set(); }
+ return {result, false};
}
template <typename T> T cast() const { return operator object().cast<T>(); }
- operator bool() const {
- if (attr) {
- return (bool) PyObject_HasAttr(obj.ptr(), key.ptr());
- } else {
- object result(PyObject_GetItem(obj.ptr(), key.ptr()), false);
- if (!result) PyErr_Clear();
- return (bool) result;
- }
- };
-
private:
handle obj;
object key;
@@ -598,6 +635,8 @@
detail::dict_iterator begin() const { return (++detail::dict_iterator(*this, 0)); }
detail::dict_iterator end() const { return detail::dict_iterator(); }
void clear() const { PyDict_Clear(ptr()); }
+ bool contains(handle key) const { return PyDict_Contains(ptr(), key.ptr()) == 1; }
+ bool contains(const char *key) const { return PyDict_Contains(ptr(), pybind11::str(key).ptr()) == 1; }
};
class list : public object {
@@ -695,6 +734,9 @@
template <typename D> accessor object_api<D>::attr(handle key) const { return {derived(), key, true}; }
template <typename D> accessor object_api<D>::attr(const char *key) const { return {derived(), key, true}; }
template <typename D> args_proxy object_api<D>::operator*() const { return {derived().ptr()}; }
+template <typename D> template <typename T> bool object_api<D>::contains(T &&key) const {
+ return attr("__contains__").template cast<object>()(std::forward<T>(key)).template cast<bool>();
+}
template <typename D>
pybind11::str object_api<D>::str() const {
diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp
index f3f557a..35981a0 100644
--- a/tests/pybind11_tests.cpp
+++ b/tests/pybind11_tests.cpp
@@ -39,7 +39,7 @@
for (const auto &initializer : initializers())
initializer(m);
- if (!m.attr("have_eigen")) m.attr("have_eigen") = py::cast(false);
+ if (!py::hasattr(m, "have_eigen")) m.attr("have_eigen") = py::cast(false);
return m.ptr();
}