Tampaknya list(a)
tidak secara keseluruhan, [x for x in a]
keseluruhan di beberapa titik, dan [*a]
keseluruhan sepanjang waktu ?
Berikut adalah ukuran n dari 0 hingga 12 dan ukuran yang dihasilkan dalam byte untuk tiga metode:
0 56 56 56
1 64 88 88
2 72 88 96
3 80 88 104
4 88 88 112
5 96 120 120
6 104 120 128
7 112 120 136
8 120 120 152
9 128 184 184
10 136 184 192
11 144 184 200
12 152 184 208
Dihitung seperti ini, dapat direproduksi di repl.it , menggunakan Python 3. 8 :
from sys import getsizeof
for n in range(13):
a = [None] * n
print(n, getsizeof(list(a)),
getsizeof([x for x in a]),
getsizeof([*a]))
Jadi: Bagaimana cara kerjanya? Bagaimana secara [*a]
keseluruhan? Sebenarnya, mekanisme apa yang digunakannya untuk membuat daftar hasil dari input yang diberikan? Apakah itu menggunakan iterator a
dan menggunakan sesuatu seperti list.append
? Di mana kode sumbernya?
( Colab dengan data dan kode yang menghasilkan gambar.)
Memperbesar menjadi lebih kecil n:
Perkecil hingga lebih besar n:
python
python-3.x
list
cpython
python-internals
Stefan Pochmann
sumber
sumber
[*a]
tampaknya berperilaku seperti menggunakanextend
daftar kosong.list(a)
beroperasi sepenuhnya dalam C; itu dapat mengalokasikan node buffer internal demi node saat iterates overa
.[x for x in a]
hanya menggunakanLIST_APPEND
banyak, jadi ini mengikuti pola "secara keseluruhan sedikit, alokasikan kembali jika perlu" dari daftar normal.[*a]
menggunakanBUILD_LIST_UNPACK
, yang ... Saya tidak tahu apa yang dilakukannya, selain ternyata terlalu banyak mengalokasikan sepanjang waktu :)list(a)
dan[*a]
identik, dan keduanya secara keseluruhan dibandingkan dengan[x for x in a]
, jadi ...sys.getsizeof
mungkin bukan alat yang tepat untuk digunakan di sini.sys.getsizeof
adalah alat yang tepat, itu hanya menunjukkan bahwalist(a)
digunakan untuk mengatur keseluruhan. Sebenarnya Apa Yang Baru Di Python 3.8 menyebutkannya: "Daftar konstruktor tidak secara keseluruhan menempatkan [...]" .Jawaban:
[*a]
secara internal melakukan C yang setara dengan :list
newlist.extend(a)
list
.Jadi, jika Anda memperluas tes Anda ke:
Cobalah online!
Anda akan melihat hasilnya
getsizeof([*a])
danl = []; l.extend(a); getsizeof(l)
sama.Ini biasanya hal yang benar untuk dilakukan; Ketika
extend
Anda biasanya berharap untuk menambahkan lebih banyak nanti, dan juga untuk pembongkaran umum, diasumsikan bahwa banyak hal akan ditambahkan satu demi satu.[*a]
bukan kasus normal; Python mengasumsikan ada beberapa item atau iterables yang ditambahkan kelist
([*a, b, c, *d]
), jadi keseluruhan lokasi menyimpan pekerjaan dalam kasus umum.Sebaliknya, yang
list
dikonstruksi dari satu, yang dapat diubah (denganlist()
) tidak boleh tumbuh atau menyusut selama penggunaan, dan keseluruhan penempatan prematur sampai terbukti sebaliknya; Python baru-baru ini memperbaiki bug yang membuat konstruktor secara keseluruhan bahkan untuk input dengan ukuran yang diketahui .Adapun
list
pemahaman, mereka secara efektif setara denganappend
s berulang , sehingga Anda melihat hasil akhir dari pola pertumbuhan keseluruhan penempatan normal saat menambahkan elemen pada suatu waktu.Agar jelas, semua ini bukan jaminan bahasa. Hanya bagaimana CPython mengimplementasikannya. Spesifikasi bahasa Python pada umumnya tidak peduli dengan pola pertumbuhan spesifik
list
(selain menjamin amortisasiO(1)
append
danpop
s dari akhir). Seperti disebutkan dalam komentar, implementasi spesifik berubah lagi di 3,9; sementara itu tidak akan mempengaruhi[*a]
, itu bisa mempengaruhi kasus-kasus lain di mana apa yang dulunya "membangun sementaratuple
item individual dan kemudianextend
dengantuple
" sekarang menjadi beberapa aplikasiLIST_APPEND
, yang dapat berubah ketika keseluruhan lokasi terjadi dan angka apa yang masuk ke dalam perhitungan.sumber
BUILD_LIST_UNPACK
, yang digunakan_PyList_Extend
sebagai C yang setara dengan pemanggilanextend
(hanya secara langsung, bukan dengan metode pencarian). Mereka menggabungkannya dengan jalan untuk membanguntuple
dengan membongkar;tuple
Tidak secara keseluruhan menempatkan dengan baik untuk pembuatan sedikit demi sedikit, sehingga mereka selalu membongkarlist
(untuk mendapatkan manfaat dari keseluruhan lokasi), dan beralih ketuple
pada akhirnya ketika itulah yang diminta.BUILD_LIST
,LIST_EXTEND
untuk setiap hal untuk dibongkar,LIST_APPEND
untuk item tunggal), alih-alih memuat segala sesuatu di stack sebelum membangun keseluruhanlist
dengan instruksi kode byte tunggal (memungkinkan compiler untuk melakukan optimasi bahwa semua-dalam-satu instruksi tidak memungkinkan, seperti menerapkan[*a, b, *c]
sebagaiLIST_EXTEND
,LIST_APPEND
,LIST_EXTEND
w / o perlu untuk membungkusb
dalam satu-tuple
untuk memenuhi persyaratanBUILD_LIST_UNPACK
).Gambaran lengkap tentang apa yang terjadi, membangun jawaban dan komentar lain (terutama jawaban ShadowRanger , yang juga menjelaskan mengapa itu dilakukan seperti itu).
Membongkar acara yang
BUILD_LIST_UNPACK
digunakan:Itu ditangani di
ceval.c
, yang membangun daftar kosong dan meluas (dengana
):_PyList_Extend
menggunakanlist_extend
:Yang memanggil
list_resize
dengan jumlah ukuran :Dan itu secara keseluruhan menempatkan sebagai berikut:
Mari kita periksa. Hitung jumlah tempat yang diharapkan dengan rumus di atas, dan hitung ukuran byte yang diharapkan dengan mengalikannya dengan 8 (karena saya menggunakan Python 64-bit di sini) dan menambahkan ukuran byte daftar kosong (yaitu, overhead konstan objek daftar) :
Keluaran:
Cocok kecuali untuk
n = 0
, yanglist_extend
sebenarnya pintasan , jadi benar-benar cocok juga:sumber
Ini akan menjadi detail implementasi dari juru bahasa CPython, dan karenanya mungkin tidak konsisten antar penerjemah lain.
Yang mengatakan, Anda bisa melihat di mana pemahaman dan
list(a)
perilaku muncul di sini:https://github.com/python/cpython/blob/master/Objects/listobject.c#L36
Khusus untuk pemahaman:
Tepat di bawah garis itu, ada
list_preallocate_exact
yang digunakan saat meneleponlist(a)
.sumber
[*a]
tidak menambahkan elemen individual satu per satu. Itu punya bytecode khusus sendiri, yang melakukan penyisipan massalextend
.[*a]