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



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

这行不通:

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:不可能用自定义类代替slice来代替listtuple等内置类型。


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

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


那么为什么slice特别呢?很高兴你这么问。欢迎来到CPython的内部。看完这篇文章,请洗手。

切片对象在slice.rst中描述。注意这两行:

. .c:var:: PyTypeObject PySlice_Type

切片对象的类型对象。类中的:class: slice相同Python层。

. .c:function:: int PySlice_Check(PyObject *ob)如果ob是切片对象,则返回true;ob不能为NULL

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

#define PySlice_Check(op) (Py_TYPE(op) == &PySlice_Type)
所以这里只允许 slice类型。这个检查实际上是在尝试使用索引协议的之后的list_subscript(和tuple subscript,…)中使用的(因此在切片上使用__index__是一个坏主意)。自定义容器类可以自由地覆盖__getitem__并使用自己的规则,但list(和tuple,…)就是这样做的。

现在,为什么不可能子类化slice ?好吧,type实际上有一个标志,表明是否可以子类化。它在这里被选中并生成您所看到的错误:

    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-(非)技能之后,我相当确定这不是严格意义上的优化。所有现有的检查和技巧仍然有效(至少是我发现的)。

在我洗手并在网上搜索之后,我发现了一些关于类似"问题"的参考文献。该说的蒂姆·彼得斯都说了:

在C中实现的任何东西都是不可子类化的,除非有人自愿做这项工作使它成为子类;没有人自愿做[在这里插入姓名]subclassable类型。wink

关于非子类可选类型的简短讨论,请参阅此主题。

几乎所有其他解释器都在不同程度上复制了这种行为:Jython, Pyston, IronPython和PyPy(没有发现它们是如何做到的,但它们确实做到了)。

我为黑魔法感到抱歉

使用Forbiddenfruit和python的内置new方法,我能够做到这一点:

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
输出:

>>> el
>>> thing

我写它作为一个挑战,只是因为每个人都说这是不可能的,我永远不会在生产代码中使用它。它有这么多的副作用,你应该重新考虑你的结构和需求

切片不能在您的return类型的方法只是不支持这一点。您可以在这里阅读更多关于__index__特殊方法的信息。我只能想出一个解决方法,直接调用类中的函数:

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__()]

这将返回el

最新更新