Python tidak membuat janji tentang kapan (jika pernah) loop ini akan berakhir. Memodifikasi set selama iterasi dapat menyebabkan elemen dilewati, elemen berulang, dan keanehan lainnya. Jangan pernah mengandalkan perilaku seperti itu.
Semua yang akan saya katakan adalah detail implementasi, dapat berubah tanpa pemberitahuan. Jika Anda menulis sebuah program yang bergantung pada semua itu, program Anda dapat merusak kombinasi implementasi Python dan versi selain CPython 3.8.2.
Penjelasan singkat mengapa loop berakhir pada 16 adalah bahwa 16 adalah elemen pertama yang kebetulan ditempatkan pada indeks tabel hash yang lebih rendah daripada elemen sebelumnya. Penjelasan lengkapnya di bawah ini.
Tabel hash internal dari set Python selalu memiliki kekuatan 2 ukuran. Untuk tabel ukuran 2 ^ n, jika tidak ada tabrakan, elemen disimpan dalam posisi di tabel hash sesuai dengan n bit paling tidak signifikan dari hash mereka. Anda dapat melihat ini diterapkan di set_add_entry
:
mask = so->mask;
i = (size_t)hash & mask;
entry = &so->table[i];
if (entry->key == NULL)
goto found_unused;
Kebanyakan hasrat Python kecil untuk diri mereka sendiri; khususnya, semua int dalam hash tes Anda untuk diri mereka sendiri. Anda dapat melihat ini diterapkan di long_hash
. Karena set Anda tidak pernah berisi dua elemen dengan bit rendah yang sama di hash, tidak ada tabrakan yang terjadi.
Set iterator Python melacak posisinya dalam set dengan indeks integer sederhana ke dalam tabel hash internal set. Ketika elemen berikutnya diminta, iterator mencari entri yang diisi dalam tabel hash mulai dari indeks itu, kemudian menetapkan indeks yang disimpan segera setelah entri yang ditemukan dan mengembalikan elemen entri. Anda dapat melihatnya di setiter_iternext
:
while (i <= mask && (entry[i].key == NULL || entry[i].key == dummy))
i++;
si->si_pos = i+1;
if (i > mask)
goto fail;
si->len--;
key = entry[i].key;
Py_INCREF(key);
return key;
Set Anda awalnya dimulai dengan tabel hash ukuran 8, dan pointer ke 0
objek int di indeks 0 di tabel hash. Iterator juga diposisikan di indeks 0. Ketika Anda mengulanginya, elemen ditambahkan ke tabel hash, masing-masing di indeks berikutnya karena di situlah hash mereka mengatakan untuk meletakkannya, dan itu selalu indeks berikutnya yang dilihat iterator. Elemen yang dihapus memiliki penanda dummy yang disimpan di posisi lamanya, untuk tujuan resolusi tabrakan. Anda dapat melihat hal itu diimplementasikan di set_discard_entry
:
entry = set_lookkey(so, key, hash);
if (entry == NULL)
return -1;
if (entry->key == NULL)
return DISCARD_NOTFOUND;
old_key = entry->key;
entry->key = dummy;
entry->hash = -1;
so->used--;
Py_DECREF(old_key);
return DISCARD_FOUND;
Ketika 4
ditambahkan ke set, jumlah elemen dan boneka di set menjadi cukup tinggi yang set_add_entry
memicu membangun kembali tabel hash, memanggil set_table_resize
:
if ((size_t)so->fill*5 < mask*3)
return 0;
return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);
so->used
adalah jumlah entri yang diisi, non-dummy dalam tabel hash, yaitu 2, sehingga set_table_resize
menerima 8 sebagai argumen kedua. Berdasarkan ini, set_table_resize
memutuskan ukuran tabel hash baru harus 16:
/* Find the smallest table size > minused. */
/* XXX speed-up with intrinsics */
size_t newsize = PySet_MINSIZE;
while (newsize <= (size_t)minused) {
newsize <<= 1; // The largest possible value is PY_SSIZE_T_MAX + 1.
}
Itu membangun kembali tabel hash dengan ukuran 16. Semua elemen masih berakhir pada indeks lama mereka di tabel hash baru, karena mereka tidak memiliki bit tinggi yang diatur dalam hash mereka.
Ketika loop berlanjut, elemen terus ditempatkan di indeks berikutnya iterator akan terlihat. Membangun kembali tabel hash lain dipicu, tetapi ukuran baru masih 16.
Pola rusak ketika loop menambahkan 16 sebagai elemen. Tidak ada indeks 16 untuk menempatkan elemen baru di. 4 bit terendah dari 16 adalah 0000, menempatkan 16 pada indeks 0. Indeks tersimpan iterator adalah 16 pada titik ini, dan ketika loop meminta elemen berikutnya dari iterator, iterator melihat bahwa ia telah melewati akhir dari tabel hash.
Iterator mengakhiri loop pada titik ini, hanya menyisakan 16
di set.
s.add(i+1)
(dan mungkin, panggilan untuks.remove(i)
) dapat mengubah urutan iterasi dari set, memengaruhi apa yang diatur iterator yang akan dilihat oleh loop berikutnya. Jangan bermutasi objek saat Anda memiliki iterator aktif.