Search code examples
pythonpython-2.7inheritancemonkeypatching

Partial inheritance - inheriting some functionality, minus a problematic method


When inheriting from a parent which has implemented the deprecated __getslice__, is there any way to get at the original slice before it gets munged?

Here is the example test case, I don't know how to override __getslice__ at a point before the relevant information is already lost. Can it be done instead with monkeypatching? Can it be done via cpython extension?

from unittest import TestCase
from mock import patch
import sys


class BigIntSlicableList(list):
    def __getslice__(self, start, stop):
        return self[start:stop:None]  # force fallback to __getitem__ 


class BigIntSlicableListTest(TestCase):

    @patch.object(BigIntSlicableList, '__getitem__')
    def test_slice_big_endpoint(self, mock):
        mylist = BigIntSlicableList([1, 2, 3])
        start, stop = sys.maxint - 1, sys.maxint + 1
        bigint_slice = slice(start, stop)
        mylist[start:stop]
        mock.assert_called_once_with(bigint_slice)
        self.assertEqual(mylist[start:stop], mylist[start:stop:])

Note: I just use list here as an ssce, I'm not really trying to make a BigList class but wondering how to get around the limitation when a parent class may have already implemented __getslice__.

The following attempt doesn't work:

class MyList(list):
    pass

del MyList.__getslice__  

The del statement raises AttributeError, even though hasattr(MyList, '__getslice__') returns True.

This question follows on from this one.


Solution

  • You can't, not with a monkeypatch on a type defined in C. That's because list doesn't have a __getslice__. It instead fills the sq_slice slot in the PySequenceMethods structure. Overriding is allowed, but you cannot make it go away.

    Even if you inherited from a Python implementation, monkey patching would come down to deleting the __getslice__ method from the original class.

    Your only option is to provide your own version, and that includes the sys.maxsize limitations. I prefer using a slice() object for such an override:

    def __getslice__(self, start, stop):
        return self[slice(start, stop)]
    

    provided the base type knows how to handle a slice object (which it has to do if it wanted to support strides).

    You can avoid the __getslice__ method altogether by using the three-element slice notation or by passing in an explicit slice() object:

    mylist[start:stop:None]
    mylist[slice(start, stop)]