Tuple slicing tidak mengembalikan objek baru sebagai lawan dari slicing list

12

Dalam Python (2 dan 3). Setiap kali kita menggunakan daftar slicing, ia mengembalikan objek baru, misalnya:

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

Keluaran

>>> 140344378384464
>>> 140344378387272

Jika hal yang sama diulangi dengan tuple, objek yang sama dikembalikan, misalnya:

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

Keluaran

>>> 140344379214896
>>> 140344379214896

Akan lebih bagus jika seseorang dapat menjelaskan mengapa ini terjadi, sepanjang pengalaman Python saya, saya mendapat kesan slice kosong mengembalikan objek baru.

Pemahaman saya adalah bahwa ia mengembalikan objek yang sama karena tupel tidak berubah dan tidak ada gunanya membuat salinan baru. Tetapi sekali lagi, itu tidak disebutkan dalam dokumen di mana pun.

Vijay Jangir
sumber
l2 = tuple(iter(l1))bypass optimasi
Chris_Rands
Melihat c-api untukPyTuple_GetSlice didokumentasikan secara tidak akurat setelah melihat pertanyaan Anda. Documents kini telah diperbaiki (ini adalah bpo issue38557 ).
wim

Jawaban:

13

Implementasi bebas untuk mengembalikan instance identik untuk tipe yang tidak dapat diubah (dalam CPython, Anda terkadang dapat melihat optimisasi serupa untuk string dan integer). Karena objek tidak dapat diubah, tidak ada dalam kode pengguna yang perlu peduli apakah itu menyimpan instance unik atau hanya referensi lain ke instance yang ada.

Anda dapat menemukan hubungan arus pendek dalam kode C di sini .

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 */
        }
    ...

Ini adalah detail implementasi, perhatikan bahwa pypy tidak melakukan hal yang sama.

wim
sumber
Terima kasih @wim. Ini masuk akal sekarang. Hanya satu hal di luar topik karena saya tidak berpengalaman dalam C. Apa yang sebenarnya a-> ob_item lakukan? Saya mencoba mencarinya. tapi yang bisa saya mengerti adalah dibutuhkan alamat "a" dan memindahkannya "ob_item" ke depan. Pemahaman saya adalah ob_item menyimpan jumlah alamat penyimpanan yang membuat item "1". #offTheTopic
Vijay Jangir
2
Mungkin membantu untuk melihat typedef untuk tuple, di sini . Jadi a->ob_itemseperti (*a).ob_item, yaitu mendapat anggota dipanggil ob_itemdari PyTupleObjectyang menunjuk ke, dan + ilow kemudian maju ke awal irisan.
wim
3

Ini detail implementasi. Karena daftar bisa berubah, l1[:] harus membuat salinan, karena Anda tidak akan mengharapkan perubahan l2mempengaruhi l1.

Karena tuple tidak dapat diubah , tidak ada yang dapat Anda lakukan untuk t2memengaruhi t1dengan cara apa pun yang terlihat, sehingga kompiler bebas (tetapi tidak diharuskan ) untuk menggunakan objek yang sama untuk t1dan t1[:].

chepner
sumber
1

Dalam Python 3. * my_list[:]adalah gula sintaksis untuk di type(my_list).__getitem__(mylist, slice_object)mana: slice_objectadalah objek irisan yang dibangun dari my_listatribut (panjang) dan ekspresi [:]. Objek yang berperilaku seperti ini disebut subscriptable dalam model data Python lihat di sini . Untuk daftar dan tupel __getitem__adalah metode bawaan.

Dalam CPython, dan untuk daftar dan tupel, __getitem__ditafsirkan oleh operasi bytecode BINARY_SUBSCRyang diimplementasikan untuk tupel di sini dan untuk daftar di sini .

Dalam hal tuple, berjalan melalui kode Anda akan melihat bahwa dalam blok kode ini , static PyObject* tuplesubscript(PyTupleObject* self, PyObject* item)akan mengembalikan referensi ke yang sama PyTupleObjectyang didapatnya sebagai argumen input, jika item bertipe PySlicedan irisan mengevaluasi ke seluruh tuple.

    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. */
        ...
}

Sekarang Anda memeriksa kode static PyObject * list_subscript(PyListObject* self, PyObject* item)dan melihat sendiri bahwa apa pun slice, objek daftar baru selalu dikembalikan.

Fakher Mokadem
sumber
1
Perhatikan bahwa ini berbeda di 2.7 , di mana start:stopirisan pada tipe bawaan, termasuk tup[:], tidak melalui BINARY_SUBSCR. Pengecilan yang diperpanjang start:stop:steptidak melalui berlangganan.
wim
Oke, terima kasih akan memperbarui untuk menentukan versi python.
Fakher Mokadem
0

Tidak yakin tentang ini tetapi tampaknya Python memberi Anda pointer baru ke objek yang sama untuk menghindari penyalinan karena tupel identik (dan karena objek adalah tupel, itu tidak berubah).

michotross
sumber