Library of Assembled Shared Sources
 
Loading...
Searching...
No Matches
Subscript Protocol

Detailed Description

Support for index- and slice-based access, assignment and deletion.

To support obj[key] subscription on an exported class, assign your __getitem__, __setitem__ and __delitem__ overloads to one of two families of special slots:

operation sequence protocol (adjusted index) mapping protocol (raw key)
v = obj[i] methods::seq_getitem_ (= methods::_getitem_) methods::map_getitem_
obj[i] = v methods::seq_setitem_ (= methods::_setitem_) methods::map_setitem_
del obj[i] n/a methods::map_delitem_

Integer indices only

Assign a getitem(Py_ssize_t) method to methods::_getitem_. Python adjusts negative indices automatically before calling your method, so that obj[-1] will be adjusted to size() - 1. Your method only needs to check bounds, and raise an IndexError if the index is out of range.

Default iteration for x in obj works automatically through this slot.

std::string myvector_seq_getitem(const MyVector& self, Py_ssize_t index)
{
if (index < 0 || std::cmp_greater_equal(index, self.size()))
{
throw lass::python::PythonException(PyExc_IndexError, "index out of range");
}
return self.at(static_cast<size_t>(index));
}
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_seq_getitem, methods::seq_getitem_)
C++ exception type that holds a Python exception.
#define PY_CLASS_FREE_METHOD_NAME(i_cppClass, f_cppFreeMethod, s_methodName)
Export a free function as a Python method, with custom Python name.
const lass::python::impl::SsizeArgSlot seq_getitem_("__seq_getitem__", Py_sq_item)
__getitem__ method (get item by index)

If your method already throws a std::out_of_range exception for out of bound indices, like std::vector<T>::at, this will be automatically translated to an IndexError in Python.

If the index uses a type other than Py_ssize_t, you may get an OverflowError instead of IndexError if an index doesn't fit the type. In particular, with size_t you will get an OverflowError for negative numbers. To be fully compliant, roll your own bounds checks as above. But if the OverflowError is acceptable, the method shrinks to:

std::string myvector_seq_getitem(const MyVector& self, size_t index)
{
return self.at(index);
}
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_seq_getitem, methods::seq_getitem_)

Slices

slice arguments can only be received through the mapping protocol, as a Slice parameter. Assign a getslice(Slice) method to methods::map_getitem_.

std::vector<std::string> myvector_getslice(const MyVector& self, Slice slice)
{
auto sliceLength = slice.adjustIndices(self.size());
auto index = slice.start;
std::vector<std::string> result;
result.reserve(sliceLength);
for (Py_ssize_t i = 0; i < sliceLength; ++i)
{
result.push_back(self.at(static_cast<size_t>(index)));
index += slice.step;
}
return result;
}
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_getslice, methods::map_getitem_)
const lass::python::impl::BinarySlot map_getitem_("__map_getitem__", Py_mp_subscript)
__getitem__ method (get item by key)
Note
This has an important consequence: once a class has any __getitem__ method on the methods::map_getitem_ slot, Python ignores the methods::seq_getitem_ for subscription. Move the integer overload to methods::map_getitem_ too, but Python will no longer automatically adjust negative indices! Use adjustIndex() to manually adjust the negative indices; as a bonus, it will also raise IndexError for out-of-range indices.
std::string myvector_map_getitem(const MyVector& self, Py_ssize_t index)
{
index = adjustIndex(index, self.size());
return self.at(static_cast<size_t>(index));
}
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_map_getitem, methods::map_getitem_)
Py_ssize_t adjustIndex(Py_ssize_t index, Py_ssize_t sequenceLength)
Helper to adjust negative sequence indices.
Definition subscript.cpp:90

Iteration

Without a __getitem__ on the methods::seq_getitem_ slot, default iteration no longer works. You have two options:

  1. Register a proper __iter__ function. See Python Iterators for more details:

    auto makeMemberRangeViewFactory(GetIterator begin, GetIterator end)
    Returns a callable creating a MemberRangeView iterating over (self->*begin)() to (self->*end)()
    const lass::python::impl::IterSlot _iter_("__iter__", Py_tp_iter)
    __iter__ method (iterator)
  2. Also register a __getitem__ on methods::seq_getitem_. But don't use adjustIndex() for that one: for callers of PySequence_GetItem(obj, index) with index < -len(obj), the index would be adjusted twice, potentially bringing the index in-range while it still should have been out-of-range.

Assignment and Deletion

Assignment and deletion follow the same rules through methods::map_setitem_ and methods::map_delitem_. Under the hood, they share the same Py_mp_ass_subscript slot, but a __setitem__ overload has key and value parameters, while a __delitem__ overload takes only a key parameter.

Putting it all together:

PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_getitem, methods::map_getitem_) // (Py_ssize_t) -> T, uses adjustIndex()
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_getslice, methods::map_getitem_) // (Slice) -> std::vector<T>
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_setitem, methods::map_setitem_) // (Py_ssize_t, T), uses adjustIndex()
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_setslice, methods::map_setitem_) // (Slice, std::vector<T>)
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_delitem, methods::map_delitem_) // (Py_ssize_t), uses adjustIndex()
PY_CLASS_FREE_METHOD_NAME(MyVector, myvector_delslice, methods::map_delitem_) // (Slice)
const lass::python::impl::ObjObjArgSlot map_delitem_
__delitem__ method (delete item by key, shares same slot as __setitem__)
const lass::python::impl::ObjObjArgSlot map_setitem_("__map_setitem__", Py_mp_ass_subscript)
__setitem__ method (set item by key)
See also
Python Iterators

Classes

struct  lass::python::Slice
 Helper type to get or return Python slice objects. More...
 

Functions

Py_ssize_t lass::python::adjustIndex (Py_ssize_t index, Py_ssize_t sequenceLength)
 Helper to adjust negative sequence indices.
 
bool lass::python::adjustIndexEx (Py_ssize_t *index, Py_ssize_t sequenceLength)
 Helper to adjust negative sequence indices.
 

Function Documentation

◆ adjustIndex()

LASS_PYTHON_DLL Py_ssize_t lass::python::adjustIndex ( Py_ssize_t index,
Py_ssize_t sequenceLength )

Helper to adjust negative sequence indices.

Use adjustIndex() to adjust the raw index argument of functions that are assigned to methods::map_getitem_, methods::map_setitem_, or methods::map_delitem_. Negative indices will be adjusted to start counting from the end of the sequence.

If the adjusted index is out of range of the sequence, a C++ PythonException exception will be thrown, containing a Python IndexError exception.

See Subscript Protocol for more details.

Note
Only use in methods on the methods::map_* slots: the sequence slots receive already-adjusted indices.
Parameters
[in]indexthe raw index to be adjusted
[in]sequenceLengthsize of the sequence to adjust the index for
Returns
the adjusted index
Exceptions
PythonExceptionwith an IndexError if adjusted index is outside [0, sequenceLength)
Precondition
sequenceLength >= 0
See also
Subscript Protocol
Slice
adjustIndexEx

Definition at line 90 of file subscript.cpp.

References adjustIndexEx(), and lass::python::impl::fetchAndThrowPythonException().

◆ adjustIndexEx()

LASS_PYTHON_DLL bool lass::python::adjustIndexEx ( Py_ssize_t * index,
Py_ssize_t sequenceLength )

Helper to adjust negative sequence indices.

Similar to adjustIndex(), but modifies index in-place, and returns false if the adjusted index is out of range.

See Subscript Protocol for more details.

Note
Most user code should use adjustIndex().
Only use in methods on the methods::map_* slots: the sequence slots receive already-adjusted indices.
Parameters
[in,out]indexpointer to the raw index to be adjusted
[in]sequenceLengthsize of the sequence to adjust the index for
Returns
  • true if the adjusted index is within range;
  • false if the adjusted index is out of range: an IndexError Python exception will be set.
Precondition
index != nullptr
sequenceLength >= 0
Postcondition
On failure, *index is unchanged
See also
Subscript Protocol
Slice
adjustIndex

Definition at line 69 of file subscript.cpp.

Referenced by adjustIndex().