Make Class Mutable by Subscription or Implement Views

Viewed 31

Consider the following Python class definition:

class Custom:
    def __init__(self, items):
        self.items = items
        
    def __getitem__(self, key):
        return Custom(items[key])

As an example for what I want to achieve is the following code snippet to print "true" instead of "false":

custom = Custom([1, 2, 3, 4])
view = custom[0:2]
view.items[0] = 0
print(custom.items[0] == 0)

This is, I want to be able to subscript my class (which basically consists of lists only) in a way that makes __getitem__() return "views" of the instances in the sense that changes to the lists of the views propagate to the lists of the original instance.

I want my class to behave with its saved lists exactly like e.g. numpy arrays behave with the values it saves. The following prints "true":

array = np.array([1, 2, 3, 4])
view = array[0:2]
view[0] = 0
array == 0

How can I achieve this? Thanks!

2 Answers

Borrowing from this answer https://stackoverflow.com/a/3485490/10035985 :

import collections.abc


class ListSlice(collections.abc.Sequence):
    def __init__(self, alist, start, alen):
        self.alist = alist
        self.start = start
        self.alen = alen

    def __len__(self):
        return self.alen

    def adj(self, i):
        while i < 0:
            i += self.alen
        if i >= self.alen:
            raise IndexError
        return i + self.start

    def __getitem__(self, i):
        return self.alist[self.adj(i)]

    def __setitem__(self, i, v):
        self.alist[self.adj(i)] = v

    def __delitem__(self, i, v):
        del self.alist[self.adj(i)]
        self.alen -= 1

    def insert(self, i, v):
        self.alist.insert(self.adj(i), v)
        self.alen += 1


class Custom:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, key):
        if isinstance(key, slice):
            return ListSlice(self.items, key.start, key.stop - key.start)
        return self.items[key]


custom = Custom([1, 2, 3, 4])
view = custom[1:3]
view[0] = 0
print(custom.items[1] == 0)
print(custom.items)

Prints:

True
[1, 0, 3, 4]

When we write

view[0] = 0

We're not calling __getitem__. We're calling __setitem__. That line is roughly equivalent to

view.__setitem__(0, 0)

So you need to implement that method

class Custom:
    def __init__(self, items):
        self.items = items
    
    def __getitem__(self, key):
        return Custom(self.items[key])

    def __setitem__(self, key, value):
        self.items[key] = value
        # ... or whatever wrapping/unwrapping you want to do with the value
Related