创建行为类似于切片的对象

2022-03-26 00:00:00 python slice

问题描述

如何在适当的时候使类将其自身表示为切片?

这不起作用:

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')
>>> 'hello world'[thing]
'el'

实际产量:

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

slice继承也不起作用。


解决方案

TLDR:无法使自定义类替换listtuple等内置类型的slice


__index__方法的存在纯粹是为了提供一个索引,根据定义,它是python中的一个整数(请参见the Data Model)。不能使用它将对象解析为slice

恐怕slice似乎是由python专门处理的。该接口需要实际的片;仅提供其签名(还包括indices方法)是不够的。正如您已经发现的,您不能从它继承,因此您不能创建新的slice类型。即使是Cython也不允许您从它继承。


那么slice为什么特别呢?很高兴你这么问。欢迎来到CPython的内部。请在阅读本文后洗手。

因此切片对象在slice.rst中描述。注意这两个家伙:

..C:var::PyTypeObject PySlice_Type

切片对象的类型对象。这与:类:slice中的 Python层。

..C:Function::Int PySlice_Check(PyObject*ob) 如果ob是切片对象,则返回True;ob不能为空。

现在,这实际上在sliceobject.h中实现为:

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

因此此处仅允许slice类型。此检查实际上是在尝试使用索引协议之后的list_subscript(和tuple subscript,...)中使用的(因此将__index__放在片上不是一个好主意)。自定义容器类可以自由覆盖__getitem__并使用自己的规则,但这就是list(和tuple,...)做到了。

现在,为什么不能子类slice呢?嗯,type实际上有一个标志,指示某个东西是否可以子类化。它被选中here并生成您看到的错误:

    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;
    }

我无法跟踪slice(Un)如何设置此值,但收到此错误的事实意味着它确实设置了该值。这意味着您不能将其子类。


结束语:在记住一些被遗忘已久的C-(非)-技能之后,我相当确定这不是严格意义上的优化。所有现有检查和技巧仍然有效(至少我找到的那些)。

在洗手和在互联网上翻找之后,我找到了一些关于类似"问题"的参考资料。Tim Peters has said all there is to say:

除非有人自愿执行工作,否则用C实现的任何东西都不是可子类的 使其可子类;没有人自愿将[在此处插入名称] 类型Subclassable。它肯定不在我列表的首位眨眼。

另请参阅this thread,了解有关不可子类类型的简短讨论。

几乎所有替代解释器都不同程度地复制了该行为:Jython、Pyston、IronPython和PyPy(没有了解它们是如何做到的,但它们确实做到了)。

相关文章