-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add shortcut dict::get
and optimize item access for dict
#2779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8a9b35d
e0aa141
5f0aead
6b2d1ba
fcecfae
68684d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,13 +35,15 @@ namespace accessor_policies { | |
struct sequence_item; | ||
struct list_item; | ||
struct tuple_item; | ||
struct dict_item; | ||
} // namespace accessor_policies | ||
using obj_attr_accessor = accessor<accessor_policies::obj_attr>; | ||
using str_attr_accessor = accessor<accessor_policies::str_attr>; | ||
using item_accessor = accessor<accessor_policies::generic_item>; | ||
using sequence_accessor = accessor<accessor_policies::sequence_item>; | ||
using list_accessor = accessor<accessor_policies::list_item>; | ||
using tuple_accessor = accessor<accessor_policies::tuple_item>; | ||
using dict_accessor = accessor<accessor_policies::dict_item>; | ||
|
||
/// Tag and check to identify a class which implements the Python object API | ||
class pyobject_tag { }; | ||
|
@@ -716,6 +718,30 @@ struct tuple_item { | |
} | ||
} | ||
}; | ||
|
||
struct dict_item { | ||
using key_type = object; | ||
|
||
static object get(handle obj, handle key) { | ||
if (PyObject *result = PYBIND11_DICT_GET_ITEM_WITH_ERROR(obj.ptr(), key.ptr())) { | ||
return reinterpret_borrow<object>(result); | ||
} else { | ||
// NULL with an exception means exception occurred when calling | ||
// "__hash__" or "__eq__" on the key | ||
// NULL without an exception means the key wasn’t present | ||
if (!PyErr_Occurred()) | ||
// Synthesize a KeyError with the key | ||
PyErr_SetObject(PyExc_KeyError, key.ptr()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the right error to set? Should we talk about Should we even care about throwing from these two? In C++, a throw from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As noted, there's two kinds of errors:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. My bad. Though the second part is important too.
The problem is not in inserting a single element and throwing. The problem is rehashing the dictionary and throwing half-way during the operation. At that point, basically anything goes. Thus, should we care about
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rgiht, I kind of missed that. But anyway, yes, Python throws an exception so we need to propagate this as C++ exception. We're not going to change how Python deals with this? I think the current implementation follows accessing |
||
throw error_already_set(); | ||
} | ||
} | ||
|
||
static void set(handle obj, handle key, handle val) { | ||
if (PyDict_SetItem(obj.ptr(), key.ptr(), val.ptr()) != 0) { | ||
throw error_already_set(); | ||
} | ||
} | ||
}; | ||
PYBIND11_NAMESPACE_END(accessor_policies) | ||
|
||
/// STL iterator template used for tuple, list, sequence and dict | ||
|
@@ -1477,13 +1503,33 @@ class dict : public object { | |
|
||
size_t size() const { return (size_t) PyDict_Size(m_ptr); } | ||
bool empty() const { return size() == 0; } | ||
detail::dict_accessor operator[](const char *key) const { return {*this, pybind11::str(key)}; } | ||
lqf96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
detail::dict_accessor operator[](handle h) const { return {*this, reinterpret_borrow<object>(h)}; } | ||
detail::dict_iterator begin() const { return {*this, 0}; } | ||
detail::dict_iterator end() const { return {}; } | ||
void clear() /* py-non-const */ { PyDict_Clear(ptr()); } | ||
template <typename T> bool contains(T &&key) const { | ||
return PyDict_Contains(m_ptr, detail::object_or_cast(std::forward<T>(key)).ptr()) == 1; | ||
} | ||
|
||
object get(handle key, handle default_ = none()) const { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other reviewers: should this be moved out of the class definition? I think it's on the edge of being too long, but good enough and maybe nicer to keep it just as-is than to split it up into two parts. |
||
if (PyObject *result = PYBIND11_DICT_GET_ITEM_WITH_ERROR(m_ptr, key.ptr())) { | ||
return reinterpret_borrow<object>(result); | ||
} else { | ||
// NULL with an exception means exception occurred when calling | ||
// "__hash__" or "__eq__" on the key | ||
if (PyErr_Occurred()) | ||
lqf96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
throw error_already_set(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Throw or return default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #2779 (comment) |
||
// NULL without an exception means the key wasn’t present | ||
else | ||
return reinterpret_borrow<object>(default_); | ||
} | ||
} | ||
|
||
object get(const char *key, handle default_ = none()) const { | ||
lqf96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return get(pybind11::str(key), default_); | ||
} | ||
|
||
private: | ||
/// Call the `dict` Python type -- always returns a new reference | ||
static PyObject *raw_dict(PyObject *op) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.