如何使类在适当的时候将自己表示为切片?
这行不通:
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
来代替list
和tuple
等内置类型。
__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