I am working on Python 2.7 and I am trying to overload __getitem__ and __setitem__ from a class that inherits list.
Let's say I have this class A:
class A(list):
def __getitem__(self, key):
print "GET!"
def __setitem__(self, key, value):
print "SET!"
With square brackets, A.__getitem__ or A.__setitem__ should be called. Normally it is like that, but when I use [:] the parent implementation is called instead. Why? And why does [::] work?
a = A([1])
a[1] # prints GET!
a["1"] # prints GET!
a[::] # prints GET!
a[slice(None)] # prints GET!
a[:] # returns the list [1]
And the same with __setitem__:
a[1] = 2 # prints SET!
a[::] = 2 # prints SET!
a[slice(None)] = 2 # prints SET!
a[:] = [2] # changes the list
That's because in Python 2 [1][:] as well as 1-d slices with start and/or end (but not when step is specified) like [1:], [:3], or [1:3] go through __getslice__ and __setslice__ if they are implemented. If they are not implemented they will also go to __getitem__ and __setitem__). Quoting from the docs:
Notice that these methods [
__*slice__] are only invoked when a single slice with a single colon is used, and the slice method is available. For slice operations involving extended slice notation, or in absence of the slice methods,__getitem__(),__setitem__()or__delitem__()is called with a slice object as argument.
In your case you inherit them from list (list implements them) so it bypasses your __getitem__ and __setitem__ in simple slice situations.
As an example, you could override the __*slice__ methods to verify that the [:] call really goes there:
class A(list):
def __getitem__(self, key):
print "GET!"
def __setitem__(self, key, value):
print "SET!"
def __getslice__(self, i, j):
print "GETSLICE!"
def __setslice__(self, i, j, seq):
print "SETSLICE!"
However these are only called when just one slice is passed in and only if the passed slice doesn't have a step. So [::] won't go there because it has a step (even though it's implicit). But also [:,:] wouldn't go into these because it is translated to tuple(slice(None), slice(None)) which isn't a simple slice but a tuple of slices. It also doesn't go into these __*slice__ methods if you pass in a slice instance yourself, that's why [slice(None)] even though seemingly equivalent to [:] directly goes into __*item__ instead of __*slice__.
[1] In Python 3 the __*slice__ methods were removed so there the [whatever] indexing will go to the __*item__ methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With