Bagaimana Daftar Python Diimplementasikan?

182

Apakah ini daftar tertaut, sebuah array? Saya mencari-cari dan hanya menemukan orang menebak. Pengetahuan C saya tidak cukup baik untuk melihat kode sumber.

Greg
sumber

Jawaban:

57

Ini adalah array dinamis . Bukti praktis: Pengindeksan (tentu saja dengan perbedaan yang sangat kecil (0,0013 µsec!)) Waktu yang sama terlepas dari indeks:

...>python -m timeit --setup="x = [None]*1000" "x[500]"
10000000 loops, best of 3: 0.0579 usec per loop

...>python -m timeit --setup="x = [None]*1000" "x[0]"
10000000 loops, best of 3: 0.0566 usec per loop

Saya akan terkejut jika IronPython atau Jython menggunakan daftar tertaut - mereka akan merusak kinerja banyak banyak perpustakaan yang digunakan secara luas dibangun dengan asumsi bahwa daftar adalah array dinamis.

user2357112 mendukung Monica
sumber
1
@Ralf: Saya tahu CPU saya (sebagian besar perangkat keras lain juga, dalam hal ini) sudah tua dan anjing lambat - sisi baiknya, saya dapat menganggap bahwa kode yang berjalan cukup cepat bagi saya cukup cepat untuk semua pengguna: D
88
@delnan: -1 "bukti praktis" Anda adalah omong kosong, seperti juga 6 upvotes. Sekitar 98% dari waktu dihabiskan untuk melakukanx=[None]*1000 , meninggalkan pengukuran dari setiap perbedaan akses daftar yang mungkin agak tidak tepat. Anda perlu memisahkan inisialisasi:-s "x=[None]*100" "x[0]"
John Machin
26
Menunjukkan bahwa itu bukan implementasi naif dari daftar tertaut. Tidak secara definitif menunjukkan bahwa ini adalah array.
Michael Mior
6
Anda dapat membacanya di sini: docs.python.org/2/faq/design.html#how-are-lists-implemented
CCoder
3
Ada jauh lebih banyak struktur dari sekadar daftar dan larik yang ditautkan, pengaturan waktu tidak ada gunanya untuk memutuskan di antara mereka.
Ross Hemsley
236

Kode C sebenarnya cukup sederhana. Memperluas satu makro dan memangkas beberapa komentar yang tidak relevan, struktur dasarnya ada di listobject.h, yang mendefinisikan daftar sebagai:

typedef struct {
    PyObject_HEAD
    Py_ssize_t ob_size;

    /* Vector of pointers to list elements.  list[0] is ob_item[0], etc. */
    PyObject **ob_item;

    /* ob_item contains space for 'allocated' elements.  The number
     * currently in use is ob_size.
     * Invariants:
     *     0 <= ob_size <= allocated
     *     len(list) == ob_size
     *     ob_item == NULL implies ob_size == allocated == 0
     */
    Py_ssize_t allocated;
} PyListObject;

PyObject_HEADberisi jumlah referensi dan pengenal tipe. Jadi, ini adalah vektor / array yang secara keseluruhan ditempatkan. Kode untuk mengubah ukuran array seperti itu ketika sudah penuh listobject.c. Sebenarnya tidak menggandakan array, tetapi tumbuh dengan mengalokasikan

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
new_allocated += newsize;

untuk kapasitas setiap kali, di mana newsizeukuran yang diminta (tidak harus allocated + 1karena Anda dapat extenddengan jumlah elemen yang sewenang-wenang alih-alih appendmengumpulkannya satu per satu).

Lihat juga FAQ Python .

Fred Foo
sumber
6
Jadi, ketika iterasi dari daftar python lambat seperti daftar tertaut, karena setiap entri hanyalah sebuah pointer, sehingga setiap elemen kemungkinan besar akan menyebabkan cache miss.
Kr0e
9
@ Kr0e: tidak jika elemen berikutnya adalah objek yang sama :) Tetapi jika Anda membutuhkan struktur data yang lebih kecil / cache-friendly, arraymodul atau NumPy lebih disukai.
Fred Foo
@ Kr0e Saya tidak akan mengatakan mengulangi daftar sama lambatnya dengan daftar yang ditautkan, tetapi mengulangi nilai - nilai dari daftar yang ditautkan adalah lambat seperti daftar yang ditautkan, dengan peringatan yang disebutkan Fred. Misalnya, pengulangan pada daftar untuk menyalinnya ke daftar lain harus lebih cepat daripada daftar yang ditautkan.
Ganea Dan Andrei
35

Dalam CPython, daftar adalah array dari pointer. Implementasi lain dari Python dapat memilih untuk menyimpannya dengan cara yang berbeda.

Amber
sumber
32

Ini tergantung pada implementasi, tetapi IIRC:

  • CPython menggunakan array pointer
  • Jython menggunakan ArrayList
  • IronPython rupanya juga menggunakan array. Anda dapat menelusuri kode sumber untuk mencari tahu.

Dengan demikian mereka semua memiliki O (1) akses acak.

NullUserException
sumber
1
Implementasi tergantung seperti pada interpreter python yang mengimplementasikan daftar sebagai daftar tertaut akan menjadi implementasi yang valid dari bahasa python? Dengan kata lain: O (1) akses acak ke daftar tidak dijamin? Bukankah itu membuatnya mustahil untuk menulis kode efisien tanpa mengandalkan detail implementasi?
sepp2k
2
@sepp Saya percaya daftar di Python hanya koleksi yang dipesan; implementasi dan / atau persyaratan kinerja dari implementasi tersebut tidak secara eksplisit dinyatakan
NullUserException
6
@ sppe2k: Karena Python tidak benar-benar memiliki spesifikasi standar atau formal (walaupun ada beberapa dokumen yang mengatakan "... dijamin untuk ..."), Anda tidak dapat 100% yakin seperti dalam "ini dijamin oleh selembar kertas ". Tetapi karena O(1)pengindeksan daftar adalah asumsi yang cukup umum dan valid, tidak ada implementasi yang berani melanggarnya.
@ Paul Ia mengatakan apa-apa tentang bagaimana implementasi daftar yang mendasarinya harus dilakukan.
NullUserException
Itu tidak terjadi untuk menentukan waktu berjalan O besar hal. Spesifikasi sintaksis bahasa tidak harus berarti hal yang sama dengan detail implementasi, itu hanya terjadi sering terjadi.
Paul McMillan
26

Saya akan menyarankan artikel Laurent Luce "implementasi daftar Python" . Sangat berguna bagi saya karena penulis menjelaskan bagaimana daftar diimplementasikan dalam CPython dan menggunakan diagram yang sangat baik untuk tujuan ini.

Daftar struktur objek C

Objek daftar di CPython diwakili oleh struktur C berikut. ob_itemadalah daftar pointer ke elemen daftar. dialokasikan adalah jumlah slot yang dialokasikan dalam memori.

typedef struct {
    PyObject_VAR_HEAD
    PyObject **ob_item;
    Py_ssize_t allocated;
} PyListObject;

Penting untuk memperhatikan perbedaan antara slot yang dialokasikan dan ukuran daftar. Ukuran daftar sama dengan len(l). Jumlah slot yang dialokasikan adalah apa yang telah dialokasikan dalam memori. Seringkali, Anda akan melihat bahwa alokasi dapat lebih besar dari ukuran. Ini untuk menghindari keharusan memanggil reallocsetiap kali elemen baru ditambahkan ke daftar.

...

Menambahkan

Kami menambahkan integer ke dalam daftar: l.append(1). Apa yang terjadi?
masukkan deskripsi gambar di sini

Kami terus dengan menambahkan satu elemen lagi: l.append(2). list_resizedipanggil dengan n + 1 = 2 tetapi karena ukuran yang dialokasikan adalah 4, tidak perlu mengalokasikan lebih banyak memori. Hal yang sama terjadi ketika kita menambahkan 2 bilangan bulat lainnya: l.append(3), l.append(4). Diagram berikut menunjukkan apa yang kita miliki sejauh ini.

masukkan deskripsi gambar di sini

...

Memasukkan

Mari kita masukkan integer baru (5) di posisi 1: l.insert(1,5)dan lihat apa yang terjadi secara internal.masukkan deskripsi gambar di sini

...

Pop

Ketika Anda pop elemen terakhir: l.pop(), listpop()disebut. list_resizedisebut di dalam listpop()dan jika ukuran baru kurang dari setengah dari ukuran yang dialokasikan maka daftar menyusut.masukkan deskripsi gambar di sini

Anda dapat mengamati bahwa slot 4 masih menunjuk ke integer tetapi yang penting adalah ukuran daftar yang sekarang 4. Mari kita pop satu elemen lagi. Dalam list_resize(), ukuran - 1 = 4 - 1 = 3 kurang dari setengah slot yang dialokasikan sehingga daftar menyusut menjadi 6 slot dan ukuran baru daftar sekarang 3.

Anda dapat mengamati bahwa slot 3 dan 4 masih menunjuk ke beberapa bilangan bulat tetapi yang penting adalah ukuran daftar yang sekarang 3.masukkan deskripsi gambar di sini

...

Hapus Python daftar objek memiliki metode untuk menghapus elemen tertentu: l.remove(5).masukkan deskripsi gambar di sini

Lesya
sumber
Terima kasih, saya mengerti bagian tautan daftar ini sekarang. Daftar python adalah aggregation, bukan composition. Saya berharap ada daftar komposisi juga.
shuva
22

Menurut dokumentasi ,

Daftar Python adalah array yang benar-benar panjang variabel, bukan daftar tertaut gaya Lisp.

ravi77o
sumber
5

Seperti yang telah dinyatakan oleh orang lain di atas, daftar (ketika cukup besar) dilaksanakan dengan mengalokasikan jumlah ruang yang tetap, dan, jika ruang itu harus diisi, mengalokasikan jumlah ruang yang lebih besar dan menyalin elemen-elemen tersebut.

Untuk memahami mengapa metode ini diamortisasi O (1), tanpa kehilangan sifat umum, asumsikan kita telah memasukkan elemen = 2 ^ n, dan sekarang kita harus menggandakan tabel kita menjadi ukuran 2 ^ (n +1). Itu berarti kami sedang melakukan 2 ^ (n +1) operasi. Salinan terakhir, kami melakukan 2 ^ operasi. Sebelum itu kami melakukan 2 ^ (n-1) ... hingga 8,4,2,1. Sekarang, jika kita tambahkan ini, kita mendapatkan 1 + 2 + 4 + 8 + ... + 2 ^ (n + 1) = 2 ^ (n + 2) - 1 <4 * 2 ^ n = O (2 ^ n) = O (a) total penyisipan (yaitu O (1) waktu diamortisasi). Juga, harus dicatat bahwa jika tabel memungkinkan penghapusan, penyusutan tabel harus dilakukan pada faktor yang berbeda (misalnya 3x)

RussellStewart
sumber
Sejauh yang saya mengerti, tidak ada penyalinan elemen yang lebih tua. Lebih banyak ruang dialokasikan, tetapi ruang baru tidak bersebelahan dengan ruang yang sudah digunakan, dan hanya elemen yang lebih baru yang akan disalin disalin ke ruang baru. Tolong koreksi saya jika saya salah.
Tushar Vazirani
1

Daftar dalam Python adalah sesuatu seperti array, tempat Anda bisa menyimpan banyak nilai. Daftar bisa berubah yang berarti Anda dapat mengubahnya. Hal yang lebih penting yang harus Anda ketahui, ketika kita membuat daftar, Python secara otomatis membuat reference_id untuk variabel daftar itu. Jika Anda mengubahnya dengan menetapkan variabel lain, daftar utama akan berubah. Mari kita coba dengan sebuah contoh:

list_one = [1,2,3,4]

my_list = list_one

#my_list: [1,2,3,4]

my_list.append("new")

#my_list: [1,2,3,4,'new']
#list_one: [1,2,3,4,'new']

Kami menambahkan my_listtetapi daftar utama kami telah berubah. Daftar mean itu tidak menetapkan sebagai daftar salinan ditugaskan sebagai referensi.

hasib
sumber
0

Dalam daftar CPython diimplementasikan sebagai array dinamis, dan karena itu ketika kita menambahkan pada saat itu tidak hanya satu makro ditambahkan tetapi lebih banyak ruang dialokasikan sehingga setiap kali ruang baru tidak boleh ditambahkan.

gaurav
sumber