2016-10-11 35 views
6

如何在合适的情况下让一个班级将自己表现为切片?制作一个表现如同一个切片的物体

这不起作用:

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继承也不起作用。

+0

好,从它看起来像'__index __()'强调返回一个'int'而不是'slice',并且它在返回单个整数时工作正常。 –

+0

顺便说一句,你为什么要这么做? – MisterMiyagi

+2

我正在制作一种应该像切片一样行为的丰富切片,但也有一些扩展功能。 – wim

回答

5

TLDR:使用自定义类替换slice的内置类型(如listtuple)是不可能的。


__index__方法纯粹的存在是为了提供一种索引,其是由定义在python的整数(参见the Data Model)。您无法使用它将对象解析为slice

恐怕slice似乎是由python专门处理。界面需要一个实际的切片;提供其签名(其中还包括indices方法)是不够的。正如你发现的那样,你不能继承它,所以你不能创建新类型的slice。即使Cython也不会让你继承它。


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

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

。C:VAR :: PyTypeObject PySlice_Type

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

.. 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实际上有一个标志,指示是否可以分类。检查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-(非)-skills后,我相当肯定这是不是严格意义上的优化。所有现有的检查和技巧仍然有效(至少是我发现的)。

在洗完手和在互联网上四处挖掘之后,我发现了一些类似“问题”的参考。 Tim Peters has said all there is to say:

用C语言实现没有什么是子类化,除非有人志愿者工作 使它子类化;没有人自告奋勇地将[插入名称] 类型子分类。它肯定不在我的名单的顶部wink

另请参阅this thread关于非子类类型的简短讨论。

几乎所有的替代口译复制行为在不同程度:JythonPystonIronPython和PyPy(没搞清楚他们是如何做到这一点,但是他们这样做)。

+1

您能否提供一个需要实际切片的接口的参考,并且以某种方式欺骗它与子类是不可能的? “看起来你做不到”这不是一个特别令人信服的答案。 – wim

+0

我不是说你好像不能这样做。我说你必须提供一个切片,而且你不能创建新的切片类型。当你无法创建子类时,用子类来诱惑它是不可能的。也就是说,我正在查看源代码,看看是否有确切的东西出现。 – MisterMiyagi

+0

这似乎违反了鸭子打字。如果'__getitem__'接受一个切片对象(它确实),它应该可以接受一些像切片一样的东西。 – wim

-2

切片不能在您的return类型中,因为该方法不支持此功能。您可以阅读有关__index__special method here的更多信息。 我只能拿出,在你的类直接调用功能的解决方法:

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

+1

不,它被左侧的'__getitem__'隐式调用。你的建议等同于定义像as_slice(self):return slice(self.start,self.stop)这样的'as_slice'方法,这是我意识到的解决方法,但不是我想要的。 – wim

+0

如果你已经阅读了允许任何对象用于切片的PEP 357,你会知道'__index__' *的返回类型具有*为'int'或'long'。切片不能在那里工作。 – Mangohero1

+1

调用'__index__'有什么用?他可以通过一个切片 - 无论哪种方式,它不会是自定义类。 – MisterMiyagi

3

我很抱歉黑暗魔法

使用Forbiddenfruit和Python 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 

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

+0

哦,是的,monkeypatching。嗯。不幸的是,这种黑客行为并不适用于我想要使用的方法('__or__','__and__'等),但无论如何都有提示。 – wim

+0

这正在改变切片,而不是创建一个新的类。之后可以做'slice(2,3,4).otherstuff','type(MyThing(1,2,3))'实际上是'slice'。 “MyThing”的实际实例仍不能用作“分片”。 – MisterMiyagi