Skip to content

Conversation

nicholasjng
Copy link
Contributor

This enables users to write syntactic sugar for in-place ops as

nb::object obj = ...;
// increment a (mutable) counter value inline.
obj.attr("count") += nb::int(1);

This is useful in bindings and wrappers because it avoids the duplication of obj.attr() calls. Templating the operator declaration allows their use in all access implementations.


Hey! This is a change I used for a small personal project lately. We previously discussed this briefly in #688 (comment), and I'd be happy about a review.

If you are interested, I will add more tests on other Impl specializations. Thanks!

@nicholasjng nicholasjng force-pushed the non-const-accessors branch 2 times, most recently from c4da166 to 65752a6 Compare July 25, 2025 20:02
@wjakob
Copy link
Owner

wjakob commented Jul 27, 2025

This looks good. I can't think of any substantially better way of implementing it. Can you increase test coverage? It would also be good to cover the other accessors (e.g. dict key-based or index-based indexing).

This enables users to write the following for in-place ops:

```cpp
nb::object obj = ...;
// increment a (mutable) counter value inline.
obj.attr("count") += nb::int(1);
```

This is useful in bindings and wrappers because it avoids the duplication of
`obj.attr()` calls. Templating the operator declaration allows their use in all
accessor implementations.

A new `test_accessor` CMake target was defined along with a Python test file,
covering the most important accessor types.
@nicholasjng nicholasjng force-pushed the non-const-accessors branch from 65752a6 to 99a218b Compare July 28, 2025 09:14
@nicholasjng
Copy link
Contributor Author

I added tests for str_item, num_item_list, and obj_item - I could not think of a test for num_item, since num_item_list is the specialization for list, and I don't know of other mutable sequence types.

Note that nb::dict with int keys does not work, because it is interpreted as a sequence:

// test_accessor.cpp
    m.def("test_num_item_accessor_inplace_mutation", []() {
        nb::dict d;
        d[0] = nb::int_(0);
        d[0] += nb::int_(1);
        return d;
    });

// test_accessor.py
def test_05_num_item_inplace_mutation():
    """
    Similar to test 01, but tests obj[n] (indexed access)
    on the C++ side.
    """
    d = t.test_num_item_accessor_inplace_mutation()
    assert len(d) == 1
    assert d.keys() == {0}
    assert d[0] == 1  # dict lookup

[...]

// raises: >      d = t.test_num_item_accessor_inplace_mutation()
//                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//         E       TypeError: dict is not a sequence

And I did not test num_item_tuple, since you protected its setter with a compile-time assert, see:

template <typename...Ts> static void set(Ts...) {
static_assert(false_v<Ts...>, "tuples are immutable!");
}
};

@wjakob
Copy link
Owner

wjakob commented Aug 10, 2025

Thanks!

@wjakob wjakob merged commit 41b21b7 into wjakob:master Aug 10, 2025
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants