1

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!

Ramen
  • 155
  • 5

2 Answers2

3

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]
Andrej Kesely
  • 168,389
  • 15
  • 48
  • 91
1

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
Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • Thanks, but this doesn't really do what I want... The snippet still returns false and the problem is that I need to save the view to a new variable while I still want to mutate the original lists even when accessing them (in part) from the view... – Ramen May 10 '21 at 21:55