Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are my subclass's __getitem__ and __setitem__ not called when I use [:]?

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 
like image 743
Aniol Avatar asked Jul 12 '18 08:07

Aniol


1 Answers

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.

like image 87
MSeifert Avatar answered Oct 04 '22 07:10

MSeifert