タプルのスライスはリストのスライスとは対照的に新しいオブジェクトを返さない


12

Python(2および3)。リストスライシングを使用すると、常に次のような新しいオブジェクトが返されます。

l1 = [1,2,3,4]
print(id(l1))
l2 = l1[:]
print(id(l2))

出力

>>> 140344378384464
>>> 140344378387272

同じことがタプルで繰り返された場合、同じオブジェクトが返されます。例:

t1 = (1,2,3,4)
t2 = t1[:]
print(id(t1))
print(id(t2))

出力

>>> 140344379214896
>>> 140344379214896

誰かがこれがなぜ起こっているのかについていくつかの光を当てることができれば素晴らしいでしょう、私のPythonの経験を通して、空のスライスが新しいオブジェクトを返すという印象を受けました。

私の理解では、タプルは不変であるのと同じオブジェクトを返し、その新しいコピーを作成する意味はありません。しかし、繰り返しになりますが、ドキュメントのどこにも記載されていません。



l2 = tuple(iter(l1))最適化をバイパス
Chris_Rands

c-apiPyTuple_GetSliceがあなたの質問を見た後に不正確に文書化されていることに気づきました。ドキュメントが修正されました(これはbpo issue38557でした)。
wim

回答:


13

実装は、不変の型と同じインスタンスを自由に返すことができます(CPythonでは、文字列と整数に対して同様の最適化が見られることがあります)。オブジェクトは変更できないため、ユーザーコードには、一意のインスタンスを保持しているか、既存のインスタンスへの別の参照を保持しているかを気にする必要はありません。

あなたはここのCコード短絡を見つけることができます。

static PyObject*
tuplesubscript(PyTupleObject* self, PyObject* item)
{
    ... /* note: irrelevant parts snipped out */
    if (start == 0 && step == 1 &&
                 slicelength == PyTuple_GET_SIZE(self) &&
                 PyTuple_CheckExact(self)) {
            Py_INCREF(self);          /* <--- increase reference count */
            return (PyObject *)self;  /* <--- return another pointer to same */
        }
    ...

これは実装の詳細です。pypyは同じことをしないことに注意してください。


@wimに感謝します。これは今では理にかなっています。私はCの経験がないので、トピックから1つだけ外します。a-> ob_itemは正確に何をしますか?探してみました。しかし、私が理解できたのは、「a」のアドレスを受け取り、それを「ob_item」を前方に移動することだけです。私の理解では、ob_itemは "1"アイテムを作成するストレージアドレスの数を保持しています。#offTheTopic
Vijay

2
ここで、タプルのtypedefを確認すると役立つ場合があります。のa->ob_itemようです(*a).ob_item。つまり、aが指しているob_itemから呼び出されるメンバーを取得しPyTupleObject、+ ilowがスライスの先頭に進みます。
wim

3

実装の詳細です。リストは変更可能であるため、変更による影響が予想されないため、はコピーを作成するl1[:] 必要があります。l2l1

ただし、タプルは不変であるため、目に見えるt2影響を与えることはできません。そのt1ため、コンパイラーは、およびに対して同じオブジェクトを使用できます(必須でありませ)。t1t1[:]


1

Python 3. *のmy_list[:]構文シュガーtype(my_list).__getitem__(mylist, slice_object)は次のとおりslice_objectです。は、my_listの属性(長さ)と式から構築されたスライスオブジェクト[:]です。このように動作するオブジェクトは、Pythonデータモデルでは添え字付きと呼ばれ、ここを参照してください。リストとタプル__getitem__の組み込みメソッドです。

CPythonでは、リストとタプルの場合、ここのタプルとここのリスト実装さ__getitem__れているバイトコード操作によって解釈されますBINARY_SUBSCR

タプルの場合、コードを歩くと、このコードブロックで、項目がタイプでスライスがタプル全体に評価される場合、入力引数として取得しstatic PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)たものへの参照が返されることがわかります。PyTupleObjectPySlice

    static PyObject*
    tuplesubscript(PyTupleObject* self, PyObject* item)
    {
        /* checks if item is an index */ 
        if (PyIndex_Check(item)) { 
            ...
        }
        /* else it is a slice */ 
        else if (PySlice_Check(item)) { 
            ...
        /* unpacks the slice into start, stop and step */ 
        if (PySlice_Unpack(item, &start, &stop, &step) < 0) { 
            return NULL;
        }
       ...
        }
        /* if we start at 0, step by 1 and end by the end of the tuple then !! look down */
        else if (start == 0 && step == 1 &&
                 slicelength == PyTuple_GET_SIZE(self) && 
                 PyTuple_CheckExact(self)) {
            Py_INCREF(self); /* increase the reference count for the tuple */
            return (PyObject *)self; /* and return a reference to the same tuple. */
        ...
}

次に、コードを調べてstatic PyObject * list_subscript(PyListObject* self, PyObject* item)、スライスが何であれ、常に新しいリストオブジェクトが返されることを確認します。


1
これは2.7とは異なり、start:stop組み込み型のスライス(を含むtup[:])はを経由しないことに注意してくださいBINARY_SUBSCRstart:stop:stepただし、拡張スライシングはサブスクリプションを通過します。
WIM

さて、Pythonのバージョンを指定するように更新します。
Fakher Mokadem、

0

これについては不明ですが、タプルは同一であるため(そしてオブジェクトはタプルであるため、不変であるため)、コピーを回避するために、Pythonは同じオブジェクトへの新しいポインターを提供しているようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.