Mengapa yang berikut ini berperilaku tak terduga dalam Python?
>>> a = 256
>>> b = 256
>>> a is b
True # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False # What happened here? Why is this False?
>>> 257 is 257
True # Yet the literal numbers compare properly
Saya menggunakan Python 2.5.2. Mencoba beberapa versi Python yang berbeda, tampaknya Python 2.3.3 memperlihatkan perilaku di atas antara 99 dan 100.
Berdasarkan hal di atas, saya dapat berhipotesis bahwa Python diimplementasikan secara internal sehingga integer "kecil" disimpan dengan cara yang berbeda dari integer yang lebih besar dan is
operator dapat membedakannya. Mengapa abstraksi bocor? Apa cara yang lebih baik untuk membandingkan dua objek arbitrer untuk melihat apakah mereka sama ketika saya tidak tahu sebelumnya apakah itu angka atau tidak?
Jawaban:
Lihatlah ini:
Inilah yang saya temukan dalam dokumentasi Python 2, "Plain Integer Objects" (Sama untuk Python 3 ):
sumber
Singkatnya - izinkan saya menekankan: Jangan gunakan
is
untuk membandingkan bilangan bulat.Ini bukan perilaku yang harus Anda harapkan.
Sebagai gantinya, gunakan
==
dan!=
bandingkan masing-masing untuk persamaan dan ketidaksetaraan. Sebagai contoh:Penjelasan
Untuk mengetahui hal ini, Anda perlu mengetahui yang berikut ini.
Pertama, apa fungsinya
is
? Ini adalah operator perbandingan. Dari dokumentasi :Dan berikut ini adalah setara.
Dari dokumentasi :
Perhatikan bahwa fakta bahwa id suatu objek dalam CPython (implementasi referensi Python) adalah lokasi dalam memori adalah detail implementasi. Implementasi lain dari Python (seperti Jython atau IronPython) dapat dengan mudah memiliki implementasi yang berbeda untuk
id
.Jadi untuk apa gunanya
is
? PEP8 menjelaskan :Pertanyaan
Anda bertanya, dan menyatakan, pertanyaan berikut (dengan kode):
Ini bukan hasil yang diharapkan. Mengapa itu diharapkan? Ini hanya berarti bahwa bilangan bulat dihargai pada yang
256
dirujuk oleh keduanyaa
danb
merupakan contoh bilangan bulat yang sama. Integer tidak dapat diubah dalam Python, sehingga mereka tidak bisa berubah. Ini seharusnya tidak berdampak pada kode apa pun. Seharusnya tidak diharapkan. Ini hanyalah detail implementasi.Tapi mungkin kita harus senang bahwa tidak ada contoh terpisah baru dalam memori setiap kali kita menyatakan nilai sama dengan 256.
Sepertinya kita sekarang memiliki dua instance integer dengan nilai
257
dalam memori. Karena bilangan bulat tidak dapat diubah, ini menghabiskan memori. Semoga saja kita tidak menyia-nyiakannya. Kami mungkin tidak. Namun perilaku ini tidak dijamin.Nah, ini sepertinya implementasi khusus Anda dari Python sedang mencoba untuk menjadi pintar dan tidak membuat bilangan bulat yang dihargai secara berlebihan kecuali jika harus. Anda tampaknya mengindikasikan Anda menggunakan implementasi referensi Python, yang merupakan CPython. Bagus untuk CPython.
Mungkin lebih baik jika CPython bisa melakukan ini secara global, jika bisa melakukannya dengan murah (karena akan ada biaya dalam pencarian), mungkin implementasi lain mungkin.
Tetapi untuk dampak pada kode, Anda seharusnya tidak peduli jika integer adalah instance dari integer. Anda hanya harus peduli apa nilai instance itu, dan Anda akan menggunakan operator perbandingan normal untuk itu, yaitu
==
.Apa
is
yangis
memeriksa apakahid
kedua objek tersebut sama. Dalam CPython,id
ini adalah lokasi dalam memori, tetapi bisa juga beberapa nomor pengidentifikasi unik lainnya dalam implementasi lain. Untuk menyatakan kembali ini dengan kode:sama dengan
Mengapa kita ingin menggunakan
is
itu?Ini bisa menjadi pemeriksaan relatif cepat untuk mengatakan, memeriksa apakah dua string sangat panjang nilainya. Tetapi karena ini berlaku pada keunikan objek, maka kami membatasi penggunaannya. Faktanya, kami sebagian besar ingin menggunakannya untuk memeriksa
None
, yang merupakan singleton (satu-satunya contoh yang ada di satu tempat di memori). Kita mungkin membuat lajang lain jika ada potensi untuk mengacaukan mereka, yang mungkin akan kita periksais
, tetapi ini relatif jarang. Berikut ini sebuah contoh (akan berfungsi dalam Python 2 dan 3) misalnyaYang mencetak:
Jadi kita melihat, dengan
is
dan seorang penjaga, kita dapat membedakan antara kapanbar
dipanggil tanpa argumen dan kapan dipanggil denganNone
. Ini adalah penggunaan-kasus utama untukis
- jangan tidak menggunakannya untuk tes untuk kesetaraan bilangan bulat, string, tupel, atau hal-hal lain seperti ini.sumber
is
- jangan menggunakannya untuk menguji kesetaraan bilangan bulat, string, tuple, atau hal-hal lain seperti ini." Namun, saya mencoba untuk mengintegrasikan mesin negara sederhana ke dalam kelas saya, dan karena negara adalah nilai-nilai buram yang satu-satunya properti yang dapat diobservasi adalah menjadi identik atau berbeda, terlihat sangat alami bagi mereka untuk dibandingkanis
. Saya berencana untuk menggunakan string yang diinternir sebagai negara. Saya lebih suka bilangan bulat polos, tapi sayangnya Python tidak dapat magang bilangan bulat (0 is 0
adalah detail implementasi).Itu tergantung pada apakah Anda ingin melihat apakah 2 hal itu sama, atau objek yang sama.
is
memeriksa untuk melihat apakah mereka adalah objek yang sama, bukan hanya sama. Int kecil mungkin menunjuk ke lokasi memori yang sama untuk efisiensi ruangAnda harus menggunakan
==
untuk membandingkan kesetaraan objek yang berubah-ubah. Anda bisa menentukan perilaku dengan__eq__
, dan__ne__
atribut.sumber
Saya terlambat tetapi, Anda ingin beberapa sumber dengan jawaban Anda? Saya akan mencoba dan mengatakan ini dengan cara pengantar sehingga lebih banyak orang dapat mengikuti.
Hal yang baik tentang CPython adalah Anda dapat benar-benar melihat sumbernya. Saya akan menggunakan tautan untuk rilis 3.5 , tetapi menemukan yang sesuai 2.x sepele.
Dalam CPython, fungsi C-API yang menangani pembuatan
int
objek baru adalahPyLong_FromLong(long v)
. Deskripsi untuk fungsi ini adalah:(Cetak miring saya)
Tidak tahu tentang Anda, tetapi saya melihat ini dan berpikir: Ayo cari array itu!
Jika Anda belum mengutak-atik kode C yang mengimplementasikan CPython, Anda harus ; semuanya cukup teratur dan mudah dibaca. Untuk kasus kami, kami perlu mencari di
Objects
subdirektori dari pohon direktori kode sumber utama .PyLong_FromLong
berurusan denganlong
objek sehingga tidak sulit untuk menyimpulkan bahwa kita perlu mengintip ke dalamlongobject.c
. Setelah melihat ke dalam, Anda mungkin berpikir semuanya kacau; mereka, tapi jangan takut, fungsi yang kita cari adalah dinginkan pada baris 230 menunggu kita untuk memeriksanya. Ini adalah fungsi bertubuh kecil sehingga badan utama (tidak termasuk deklarasi) mudah disisipkan di sini:Sekarang, kita bukan C master-code-haxxorz tetapi kita juga tidak bodoh, kita dapat melihat bahwa
CHECK_SMALL_INT(ival);
mengintip kita semua dengan menggoda; kita bisa memahaminya ada hubungannya dengan ini. Mari kita periksa:Jadi makro yang memanggil fungsi
get_small_int
jika nilainyaival
memenuhi syarat:Jadi apa
NSMALLNEGINTS
dan apaNSMALLPOSINTS
? Makro! Inilah mereka :Jadi syarat kita adalah
if (-5 <= ival && ival < 257)
panggilanget_small_int
.Selanjutnya mari kita lihat
get_small_int
dalam semua kemuliaan (well, kita hanya akan melihat tubuhnya karena di situlah hal-hal yang menarik):Oke, nyatakan a
PyObject
, nyatakan bahwa kondisi sebelumnya memegang dan menjalankan tugas:small_ints
terlihat sangat mirip dengan array yang telah kita cari, dan itu! Kita bisa saja membaca dokumentasi sialan itu dan kita akan tahu selama ini! :Jadi ya, ini orang kita. Saat Anda ingin membuat yang baru
int
dalam rentang[NSMALLNEGINTS, NSMALLPOSINTS)
Anda hanya akan mendapatkan kembali referensi ke objek yang sudah ada yang telah dialokasikan sebelumnya.Karena referensi mengacu pada objek yang sama, mengeluarkan
id()
secara langsung atau memeriksa identitasis
dengannya akan mengembalikan hal yang sama persis.Tapi, kapan mereka dialokasikan ??
Selama inisialisasi dengan
_PyLong_Init
Python akan dengan senang hati memasukkan for for do melakukan ini untuk Anda:Lihat sumbernya untuk membaca loop body!
Saya harap penjelasan saya membuat Anda C hal-hal yang jelas sekarang (pun jelas berniat).
Tapi,
257 is 257
? Ada apa?Ini sebenarnya lebih mudah dijelaskan, dan saya sudah berusaha melakukannya ; itu karena fakta bahwa Python akan menjalankan pernyataan interaktif ini sebagai satu blok:
Selama melengkapi pernyataan ini, CPython akan melihat bahwa Anda memiliki dua literal yang cocok dan akan menggunakan
PyLongObject
representasi yang sama257
. Anda dapat melihat ini jika Anda melakukan kompilasi sendiri dan memeriksa isinya:Ketika CPython melakukan operasi, sekarang hanya akan memuat objek yang sama persis:
Jadi
is
akan kembaliTrue
.sumber
Seperti yang Anda dapat memeriksa file sumber intobject.c , Python cache bilangan bulat kecil untuk efisiensi. Setiap kali Anda membuat referensi ke integer kecil, Anda merujuk integer kecil yang di-cache, bukan objek baru. 257 bukan bilangan bulat kecil, sehingga dihitung sebagai objek yang berbeda.
Lebih baik digunakan
==
untuk tujuan itu.sumber
Saya pikir hipotesis Anda benar. Eksperimen dengan
id
(identitas objek):Tampaknya angka
<= 255
diperlakukan sebagai literal dan apa pun di atas diperlakukan berbeda!sumber
Untuk objek nilai yang tidak dapat diubah, seperti int, string atau datetimes, identitas objek tidak terlalu berguna. Lebih baik memikirkan kesetaraan. Identitas pada dasarnya adalah detail implementasi untuk objek nilai - karena mereka tidak dapat diubah, tidak ada perbedaan efektif antara memiliki beberapa referensi ke objek yang sama atau beberapa objek.
sumber
Ada masalah lain yang tidak ditunjukkan dalam jawaban yang ada. Python diizinkan untuk menggabungkan dua nilai yang tidak dapat diubah, dan nilai int kecil yang dibuat sebelumnya bukan satu-satunya cara hal ini bisa terjadi. Implementasi Python tidak pernah dijamin untuk melakukan ini, tetapi mereka semua melakukannya untuk lebih dari sekedar int kecil.
Untuk satu hal, ada beberapa nilai pra-dibuat lainnya, seperti string kosong
tuple
,,str
danbytes
, dan beberapa string pendek (dalam CPython 3.6, ini adalah 256 karakter Latin-1 string tunggal). Sebagai contoh:Tetapi juga, bahkan nilai-nilai yang tidak dibuat sebelumnya bisa identik. Pertimbangkan contoh-contoh ini:
Dan ini tidak terbatas pada
int
nilai:Jelas, CPython tidak datang dengan
float
nilai yang dibuat sebelumnya untuk42.23e100
. Jadi, apa yang terjadi di sini?The CPython compiler akan menggabungkan nilai-nilai konstan beberapa jenis dikenal-berubah seperti
int
,float
,str
,bytes
, di unit kompilasi yang sama. Untuk modul, seluruh modul adalah unit kompilasi, tetapi pada penerjemah interaktif, setiap pernyataan adalah unit kompilasi yang terpisah. Karenac
dand
didefinisikan dalam pernyataan terpisah, nilainya tidak digabungkan. Karenae
danf
didefinisikan dalam pernyataan yang sama, nilainya digabungkan.Anda dapat melihat apa yang terjadi dengan membongkar bytecode. Cobalah mendefinisikan fungsi yang berfungsi
e, f = 128, 128
dan memanggilnyadis.dis
, dan Anda akan melihat bahwa ada nilai konstan tunggal(128, 128)
Anda mungkin memperhatikan bahwa kompiler telah disimpan
128
sebagai konstanta walaupun sebenarnya tidak digunakan oleh bytecode, yang memberi Anda gambaran tentang seberapa sedikit optimasi yang dilakukan kompiler CPython. Yang berarti bahwa tupel (non-kosong) sebenarnya tidak berakhir digabungkan:Menempatkan bahwa dalam fungsi,
dis
itu, dan lihat dico_consts
-ada1
dan2
, dua(1, 2)
tupel yang berbagi sama1
dan2
tetapi tidak identik, dan((1, 2), (1, 2))
tuple yang memiliki dua tupel yang sama yang berbeda.Ada satu lagi optimasi yang dilakukan CPython: string interning. Tidak seperti kompilasi pelipatan konstan, ini tidak terbatas pada literal kode sumber:
Di sisi lain, terbatas pada
str
jenisnya, dan untuk tipe penyimpanan internal "ascii compact", "compact", atau "legacy ready" , dan dalam banyak kasus hanya "ascii compact" yang akan diinternir.Bagaimanapun, aturan untuk nilai apa yang harus, mungkin, atau tidak dapat berbeda bervariasi dari implementasi ke implementasi, dan antara versi dari implementasi yang sama, dan mungkin bahkan antara menjalankan kode yang sama pada salinan yang sama dari implementasi yang sama .
Ini bisa bernilai belajar aturan untuk satu Python tertentu untuk bersenang-senang. Tetapi tidak layak mengandalkan mereka dalam kode Anda. Satu-satunya aturan aman adalah:
x is y
, gunakanx == y
)x is not y
, gunakanx != y
)Atau, dengan kata lain, hanya digunakan
is
untuk menguji lajang terdokumentasi (sepertiNone
) atau yang hanya dibuat di satu tempat dalam kode (seperti_sentinel = object()
idiom).sumber
x is y
untuk membandingkan, gunakanx == y
. Demikian juga jangan gunakanx is not y
, gunakanx != y
a=257; b=257
satu barisa is b
Benaris
adalah operator kesetaraan identitas (berfungsi sepertiid(a) == id(b)
); hanya saja dua bilangan yang sama tidak harus objek yang sama. Untuk alasan kinerja, beberapa bilangan bulat kecil akan dipo sehingga mereka akan cenderung sama (ini dapat dilakukan karena tidak dapat diubah).Operator PHP
===
, di sisi lain, digambarkan sebagai memeriksa kesetaraan dan jenis:x == y and type(x) == type(y)
sesuai komentar Paulo Freitas Ini akan cukup untuk angka umum, tetapi berbeda dariis
untuk kelas yang mendefinisikan__eq__
dengan cara yang tidak masuk akal:PHP rupanya memungkinkan hal yang sama untuk kelas "built-in" (yang saya maksud diimplementasikan pada level C, bukan dalam PHP). Penggunaan yang sedikit kurang absurd mungkin berupa objek penghitung waktu, yang memiliki nilai berbeda setiap kali digunakan sebagai angka. Cukup mengapa Anda ingin meniru Visual Basic
Now
daripada menunjukkan bahwa ini adalah evaluasi dengantime.time()
saya tidak tahu.Greg Hewgill (OP) membuat satu komentar klarifikasi "Tujuan saya adalah untuk membandingkan identitas objek, daripada kesetaraan nilai. Kecuali untuk angka, di mana saya ingin memperlakukan identitas objek sama dengan kesetaraan nilai."
Ini akan memiliki jawaban lain, karena kita harus mengkategorikan hal sebagai angka atau tidak, untuk memilih apakah kita membandingkan dengan
==
atauis
. CPython mendefinisikan protokol angka , termasuk PyNumber_Check, tetapi ini tidak dapat diakses dari Python itu sendiri.Kita bisa mencoba menggunakan
isinstance
dengan semua jenis nomor yang kita ketahui, tetapi ini pasti tidak lengkap. Modul types berisi daftar StringTypes tetapi tidak ada NumberTypes. Sejak Python 2.6, kelas angka bawaan memiliki kelas dasarnumbers.Number
, tetapi memiliki masalah yang sama:Ngomong-ngomong, NumPy akan menghasilkan contoh terpisah dari angka rendah.
Saya sebenarnya tidak tahu jawaban untuk varian pertanyaan ini. Saya kira seseorang secara teoritis dapat menggunakan ctypes untuk memanggil
PyNumber_Check
, tetapi bahkan fungsi itu telah diperdebatkan , dan tentu saja tidak portabel. Kami hanya harus kurang spesifik tentang apa yang kami uji untuk saat ini.Pada akhirnya, masalah ini berasal dari Python awalnya tidak memiliki pohon jenis dengan predikat seperti Skema
number?
, atau jenis kelas Num Haskell . memeriksa identitas objek, bukan menghargai kesetaraan. PHP juga memiliki sejarah yang penuh warna, di mana tampaknya hanya berperilaku pada objek di PHP5, tetapi tidak pada PHP4 . Tersebut adalah rasa sakit yang berkembang dari lintas bahasa (termasuk versi satu).is
===
is
sumber
Ini juga terjadi dengan string:
Sekarang semuanya tampak baik-baik saja.
Itu juga diharapkan.
Nah, itu tidak terduga.
sumber
'xx'
seperti yang diharapkan, sebagaimana adanya'xxx'
, tetapi'x x'
tidak.xx
di mana saja di sesi Python Anda, string itu sudah diinternir; dan mungkin ada heuristik yang melakukannya jika hanya menyerupai nama. Seperti halnya angka, ini bisa dilakukan karena mereka tidak berubah. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interningApa yang Baru Di Python 3.8: Perubahan perilaku Python :
sumber