Search code examples
pythonpython-2.7listslicepython-internals

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 

Solution

  • 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.