2

I've got an object that contains a large data table called _X. Data examples of various lengths sit stacked end-to-end inside _X, and a different table I've named _INDEX encodes the mapping from example number -> range in _X where that example lives.

What I want is to define a property called X with __getitem__ and __setitem__ of its own such that I can use X[i,j] to access the jth element of the ith example. This is to avoid having to write confusing verbose lines like self._X[self._INDEX[i]:self._INDEX[i+1]][j] all over the place.

I could make a wrapper class with the right __getitem__ and __setitem__ and return that from my @property function, but I'd rather not have to do that.

Pavel Komarov
  • 864
  • 10
  • 24
  • 1
    What does the property actually return? The property itself doesn't need to define `__getitem__`, just the class whose instance the property returns. – chepner Aug 12 '19 at 21:47
  • That is, `foo.X[3,4]` is really `type(foo).X.fget(foo)[3,4]`; whatever `X.fget` returns is what gets indexed, not `X` itself. – chepner Aug 12 '19 at 21:49
  • If you write a function named `X` containing the one verbose line, the rest of your code simply calls `a.X(i,j)`. Why doesn't that work? You can't avoid writing that verbose line once. No properties or special methods are needed. – Paul Cornelius Aug 12 '19 at 21:58
  • This makes me appreciate JavaScript, where I could always just overwrite whatever methods were bound. – Pavel Komarov Aug 12 '19 at 22:00
  • I don't want this to be a function, though that would effectively do the same thing. I want it to look like proper indexing to the user. This is one of several tables, and I can expose the others without any special functions. For symmetry I want this to behave just like one of those. – Pavel Komarov Aug 12 '19 at 22:01

1 Answers1

2

Define a wrapper class first that defines the desired __getitem__.

class Foo:
    def __init__(self, table, index):
        self.table = table
        self.index = index
    def __getitem__(self, k):
        i, j = k
        return self.table[self.index[i]:self.index[i+1]][j]

Then define your property

@property
def X(self):
    return Foo(self._X, self._INDEX)
chepner
  • 446,329
  • 63
  • 468
  • 610
  • Is there no cleaner way? I really don't want a whole new class. I really just want to override/intercept the `__getitem__` and `__setitem__` calls on my `._X` array. https://stackoverflow.com/questions/51159611/intercepting-getitem-calls-on-an-object-attribute – Pavel Komarov Aug 12 '19 at 22:10
  • You don't have *a* array; you have an object of some type that defines `__getitem__`, and that object stores objects of *another* type with its own `__getitem__`. – chepner Aug 12 '19 at 22:37
  • This doesn't really have to be a property, no? It can just be an attribute. – juanpa.arrivillaga Aug 12 '19 at 22:48
  • IIUC, the property is necessary to return a value whose type has the appropriate `__getitem__` defined, rather then the "original" value. – chepner Aug 12 '19 at 22:51
  • I mean just `self.x = Foo()`, then `obj.x[x,y]` would work, no? – juanpa.arrivillaga Aug 12 '19 at 22:59
  • @juanpa.arrivillaga Hm, I suppose so (though `Foo` still has to take `self._X` and `self._INDEX`, or just `self` itself, as arguments). I guess it's a trade-off between the memory used to keep a `Foo` instance around vs the cost of creating the `Foo` instance on demand. – chepner Aug 13 '19 at 02:48
  • @chepner I mean, the memory overhead is negligible, unless you are creating millions of instances of the class. In any case, you're still creating a `property` object, which has it's own memory overhead... – juanpa.arrivillaga Aug 13 '19 at 02:49