Make an object that behaves like a slice

How can we make a class that represents itself as a slice, if necessary?

This did not work:

class MyThing(object): def __init__(self, start, stop, otherstuff): self.start = start self.stop = stop self.otherstuff = otherstuff def __index__(self): return slice(self.start, self.stop) 

Expected Result:

 >>> thing = MyThing(1, 3, 'potato') >>> 'hello world'[thing] 'el' 

Actual output:

 TypeError: __index__ returned non-(int,long) (type slice) 

Inheritance from slice doesn't work either.

+6
source share
3 answers

TL; DR: it is not possible to create custom classes instead of slice for built-in types such as list and tuple .


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

I am afraid that slice apparently being handled specifically by python. The interface requires an actual cut; providing his signature (which also includes the indices method) is not enough. As you know, you cannot inherit it, therefore you cannot create new types of slice s. Even Cython will not let you inherit it.


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

Thus, slice objects are described in slice.rst . Pay attention to these two guys:

.. c: var :: PyTypeObject PySlice_Type

Type object for slice objects. This is the same as: class: slice in Python Level.

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

Now it is actually implemented in sliceobject.h as:

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

Thus, only the slice type is allowed. This check is actually used in list_subscript (and tuple subscript , ...) after trying to use the index protocol (so __index__ on a piece is a bad idea). A custom container class can overwrite __getitem__ and use its own rules, but the way list (and tuple , ...) does it.

Now, why is this not possible for a subclass of slice ? Well, type has a flag indicating whether something can be a subclass. It is checked here and generates the error you saw:

  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 was not able to determine how slice (un) sets this value, but the fact that it receives this error means that it is. This means that you cannot subclass it.


Concluding remarks. After remembering some of the long-forgotten C- (non) courses, I’m sure that this is not about optimization in the strict sense of the word. All existing checks and tricks will still work (at least the ones I found).

After washing my hands and digging on the Internet, I found several links to similar "problems." Tim Peters said all that can be said:

Nothing implemented in C is subclassed unless someone volunteers to make it a subclass; no one volunteered to work to make the [insert name here] subclass type. This was probably not at the top of my list.

Also see this thread for a brief discussion of non-classified types.

Almost all alternative interpreters repeat the behavior to varying degrees: Jython , Pyston , IronPython and PyPy (they haven't figured out how they do it, but they do).

+6
source

I apologize for dark magic

Using Forbiddenfruit and the new python built-in 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 

exit:

 >>> el >>> thing 

I wrote this as a challenge only because everyone said that it was impossible, I would never use it in working code. THIS IS SO MUCH SIDE EFFECTS, you should once again think about your structure and needs.

+4
source

A slice cannot be in your return type, as the method simply does not support this. Read more about the special __index__ method here . I could come up with a workaround that directly calls a function in your class:

 class MyThing(object): def __init__(self, start, stop, otherstuff): self.start = start self.stop = stop self.otherstuff = otherstuff def __index__(self): return slice(self.start, self.stop) thing = MyThing(1, 3, 'potato') print 'Hello World'[thing.__index__()] 

This will return el .

-2
source