2016-10-11 33 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