Skip to content

Add __dict__ to LowLevelView #7

Description

@tomas16

EDIT: originally this was a feature request to add weak reference support to the LowLevelView type. After discussion it seems like adding support for a __dict__ is a better solution.

I have a specific use case for this I will explain below. However I'm open to suggestions for alternative solutions. I looked into using special variables and couldn't come up with a solution, though I must admit I'm not very confident about my understanding of what these variables do.

Suppose I have C++ functions that return std::vector. The goal is to turn these into numpy arrays without copying any data. The challenge is I want to be able to keep the underlying C++ objects alive until no python object is referencing the LowLevelView anymore.

To illustrate the issue, consider this code:

import cppyy
import numpy as np

cppyy.cppdef("""
#include <numeric>

static std::vector<int> VectorFunc(size_t len)
{
    std::vector<int> ret(len);
    std::iota(ret.begin(), ret.end(), 0);
    return ret;
}
""")
VectorFunc = cppyy.gbl.VectorFunc


def vector_func(size):
    vec = VectorFunc(size)
    arr = np.frombuffer(vec.data(), dtype=np.int32)
    # at this point, arr is completely valid because we still have a reference to vec
    # arr.base is a LowLevelView object.
    print(arr)
    return arr


arr = vector_func(5)
print(arr)

Output:

[0 1 2 3 4]
[-1689064896       41504         499           0           4]

We know we need to keep the C++ vector alive for as long as anything is referencing the LowLevelView object pointing to vec.data(). One way to do this is to wrap the LowLevelView object with a small wrapper class that also stores a reference to vec and make sure arr.base points to an instance of this class. The issue is this wrapper class needs to "forward" the buffer protocol, i.e. it needs to present the buffer protocol from its LowLevelView member to its callers. I don't think this is currently possible. PEP688 may change this starting in 3.12.

Another way to achieve the same behavior would be to keep a reference to vec until the last reference to the LowLevelView goes out of scope. To that end, we could keep a dictionary where the keys are weak references to LowLevelViews and the values are the corresponding std::vectors. Continuing from the previous code:

import weakref

cpp_objects = {}


def vector_func2(size):
    def callback(x):
        del cpp_objects[x]

    vec = VectorFunc(size)
    llv = vec.data()
    wr = weakref.ref(llv, callback)
    cpp_objects[wr] = vec
    return np.frombuffer(llv, dtype=np.int32)


vector_func2(5)

I think this would do the trick, but it requires LowLevelView to support weak referencing.

  File "/Users/tomas/repo/oct100/python/cpycppyy_issue1.py", line 46, in <module>
    vector_func2(5)
  File "/Users/tomas/repo/oct100/python/cpycppyy_issue1.py", line 42, in vector_func2
    wr = weakref.ref(llv, callback)
TypeError: cannot create weak reference to 'cppyy.LowLevelView' object

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions