Make an object that behaves like a slice

TLDR: It's impossible to make custom classes replace slice for builtins types such as list and tuple.


The __index__ method exists purely to provide an index, which is by definition an integer in python (see the Data Model). You cannot use it for resolving an object to a slice.

I'm afraid that slice seems to be handled specially by python. The interface requires an actual slice; providing its signature (which also includes the indices method) is not sufficient. As you've found out, you cannot inherit from it, so you cannot create new types of slices. Even Cython will not allow you to inherit from it.


So why is slice special? Glad you asked. Welcome to the innards of CPython. Please wash your hands after reading this.

So slice objects are described in slice.rst. Note these two guys:

.. c:var:: PyTypeObject PySlice_Type

The type object for slice objects. This is the same as :class:slice in the Python layer.

.. c:function:: int PySlice_Check(PyObject *ob) Return true if ob is a slice object; ob must not be NULL.

Now, this is actually implemented in sliceobject.h as :

#define PySlice_Check(op) (Py_TYPE(op) == &PySlice_Type)

So only the slice type is allowed here. This check is actually used in list_subscript (and tuple subscript, ...) after attempting to use the index protocol (so having __index__ on a slice is a bad idea). A custom container class is free to overwrite __getitem__ and use its own rules, but that's how list (and tuple, ...) does it.

Now, why is it not possible to subclass slice? Well, type actually has a flag indicating whether something can be subclassed. It is checked here and generates the error you have seen:

    if (!PyType_HasFeature(base_i, Py_TPFLAGS_BASETYPE)) {
        PyErr_Format(PyExc_TypeError,
                     "type '%.100s' is not an acceptable base type",
                     base_i->tp_name);
        return NULL;
    }

I haven't been able to track down how slice (un)sets this value, but the fact that one gets this error means it does. This means you cannot subclass it.


Closing remarks: After remembering some long-forgotten C-(non)-skills, I'm fairly sure this is not about optimization in the strict sense. All existing checks and tricks would still work (at least those I've found).

After washing my hands and digging around in the internet, I've found a few references to similar "issues". Tim Peters has said all there is to say:

Nothing implemented in C is subclassable unless somebody volunteers the work to make it subclassable; nobody volunteered the work to make the [insert name here] type subclassable. It sure wasn't at the top of my list wink.

Also see this thread for a short discussion on non-subclass'able types.

Practically all alternative interpreters replicate the behavior to various degrees: Jython, Pyston, IronPython and PyPy (didn't find out how they do it, but they do).


I'M SORRY FOR THE DARK MAGIC

Using Forbiddenfruit and the python's builtin new method I was able to do this:

from forbiddenfruit import curse


class MyThing(int):
    def __new__(cls, *args, **kwargs):
        magic_slice = slice(args[0], args[1])
        curse(slice, 'otherstuff', args[2])  

        return magic_slice

thing = MyThing(1, 3, 'thing')
print 'hello world'[thing]
print thing.otherstuff

output:

>>> el
>>> thing

I wrote it as a challenge just because everybody said it is impossible, I would never use it on production code IT HAS SO MANY SIDE EFFECTS, you should think again on your structure and needs

Tags:

Python

Slice