“Is” operator berperilaku tak terduga dengan integer

509

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 isoperator 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?

Greg Hewgill
sumber
1
Lihatlah di sini > Implementasi saat ini membuat array objek integer untuk semua> integer antara -5 dan 256, ketika Anda membuat sebuah int dalam rentang itu> Anda sebenarnya hanya mendapatkan kembali referensi ke objek yang ada.
user5319825
2
Ini adalah detail implementasi khusus CPython dan perilaku yang tidak terdefinisi, gunakan dengan hati
ospider

Jawaban:

393

Lihatlah ini:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

Inilah yang saya temukan dalam dokumentasi Python 2, "Plain Integer Objects" (Sama untuk Python 3 ):

Implementasi saat ini membuat array objek integer untuk semua integer antara -5 dan 256, ketika Anda membuat int dalam rentang itu, Anda sebenarnya hanya mendapatkan kembali referensi ke objek yang ada. Jadi harus dimungkinkan untuk mengubah nilai 1. Saya menduga perilaku Python dalam kasus ini tidak terdefinisi. :-)

Cybis
sumber
46
apakah ada yang tahu bagaimana rentang itu (-5, 256) dipilih? saya tidak akan terlalu terkejut jika itu (0, 255) atau bahkan (-255, 255), tetapi kisaran 262 angka mulai -5 tampaknya mengejutkan sewenang-wenang.
Woodrow Barlow
6
@ WoodrowBarlow: -5 hanyalah heuristik untuk menangkap placeholder negatif umum, saya pikir. 0..255 mencakup array nilai byte tunggal. Ini 256 yang misterius, tapi saya kira itu untuk (dis) merakit integer ke / dari byte.
Davis Herring
3
Dari apa yang saya pahami rentang dipilih dengan melihat nilai-nilai yang umum digunakan di beberapa proyek (dan beberapa bahasa).
Tony Suffolk 66
9
Menurut reddit.com/r/Python/comments/18leav/… , rentang yang digunakan adalah [-5.100]. Itu diperluas untuk mencakup rentang penuh nilai byte - ditambah 256, karena itu mungkin angka yang umum.
mwfearnley
2
@Ashwani mencoba membaca komentar tepat di sebelah komentar Anda, diposting dua tahun sebelum Anda, dan Anda akan menemukan jawaban untuk pertanyaan Anda.
jbg
116

"Apakah" operator Python berperilaku tak terduga dengan bilangan bulat?

Singkatnya - izinkan saya menekankan: Jangan gunakan isuntuk membandingkan bilangan bulat.

Ini bukan perilaku yang harus Anda harapkan.

Sebagai gantinya, gunakan ==dan !=bandingkan masing-masing untuk persamaan dan ketidaksetaraan. Sebagai contoh:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

Penjelasan

Untuk mengetahui hal ini, Anda perlu mengetahui yang berikut ini.

Pertama, apa fungsinya is? Ini adalah operator perbandingan. Dari dokumentasi :

Operator isdan is notuji untuk identitas objek: x is ybenar jika dan hanya jika x dan y adalah objek yang sama. x is not ymenghasilkan nilai kebenaran terbalik.

Dan berikut ini adalah setara.

>>> a is b
>>> id(a) == id(b)

Dari dokumentasi :

id Kembalikan "identitas" suatu objek. Ini adalah bilangan bulat (atau bilangan bulat panjang) yang dijamin unik dan konstan untuk objek ini selama masa pakainya. Dua objek dengan masa hidup yang tidak tumpang tindih mungkin memiliki nilai yang sama id().

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 :

Perbandingan dengan orang lajang seperti Noneharus selalu dilakukan dengan isatau is not, tidak pernah dengan operator kesetaraan.

Pertanyaan

Anda bertanya, dan menyatakan, pertanyaan berikut (dengan kode):

Mengapa yang berikut ini berperilaku tak terduga dalam Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

Ini bukan hasil yang diharapkan. Mengapa itu diharapkan? Ini hanya berarti bahwa bilangan bulat dihargai pada yang 256dirujuk oleh keduanya adan bmerupakan 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.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

Sepertinya kita sekarang memiliki dua instance integer dengan nilai 257dalam memori. Karena bilangan bulat tidak dapat diubah, ini menghabiskan memori. Semoga saja kita tidak menyia-nyiakannya. Kami mungkin tidak. Namun perilaku ini tidak dijamin.

>>> 257 is 257
True           # Yet the literal numbers compare properly

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 isyang

ismemeriksa apakah idkedua objek tersebut sama. Dalam CPython, idini adalah lokasi dalam memori, tetapi bisa juga beberapa nomor pengidentifikasi unik lainnya dalam implementasi lain. Untuk menyatakan kembali ini dengan kode:

>>> a is b

sama dengan

>>> id(a) == id(b)

Mengapa kita ingin menggunakan isitu?

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 periksa is, tetapi ini relatif jarang. Berikut ini sebuah contoh (akan berfungsi dalam Python 2 dan 3) misalnya

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

Yang mencetak:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

Jadi kita melihat, dengan isdan seorang penjaga, kita dapat membedakan antara kapan bardipanggil tanpa argumen dan kapan dipanggil dengan None. Ini adalah penggunaan-kasus utama untuk is- jangan tidak menggunakannya untuk tes untuk kesetaraan bilangan bulat, string, tupel, atau hal-hal lain seperti ini.

Aaron Hall
sumber
"Ini adalah kasus penggunaan utama untuk 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 dibandingkan is. 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 0adalah detail implementasi).
Alexey
@ Alexey terdengar seperti Anda perlu enum? stackoverflow.com/questions/37601644/…
Aaron Hall
Mungkin, terima kasih, tidak mengenal mereka. Ini bisa menjadi tambahan yang tepat untuk Anda menjawab IMO.
Alexey
Mungkin menggunakan sejumlah objek bodoh seperti penjaga dalam jawaban Anda akan menjadi solusi yang lebih ringan ...
Alexey
@Alexey enum ada di pustaka standar Python 3, dan itu mungkin akan mendorong kode Anda menjadi sedikit lebih bermakna daripada penjaga biasa.
Aaron Hall
60

Itu tergantung pada apakah Anda ingin melihat apakah 2 hal itu sama, atau objek yang sama.

ismemeriksa untuk melihat apakah mereka adalah objek yang sama, bukan hanya sama. Int kecil mungkin menunjuk ke lokasi memori yang sama untuk efisiensi ruang

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Anda harus menggunakan ==untuk membandingkan kesetaraan objek yang berubah-ubah. Anda bisa menentukan perilaku dengan __eq__, dan __ne__atribut.

JimB
sumber
Jempol untuk benar-benar menjelaskan cara membandingkan objek yang arbitrer, seperti yang diminta OP !!
Joooeey
54

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 intobjek baru adalah PyLong_FromLong(long v). Deskripsi untuk fungsi ini adalah:

Implementasi saat ini membuat array objek integer untuk semua integer antara -5 dan 256, ketika Anda membuat int dalam rentang itu, Anda sebenarnya hanya mendapatkan kembali referensi ke objek yang ada . Jadi harus dimungkinkan untuk mengubah nilai 1. Saya menduga perilaku Python dalam kasus ini tidak terdefinisi. :-)

(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 Objectssubdirektori dari pohon direktori kode sumber utama .

PyLong_FromLongberurusan dengan longobjek sehingga tidak sulit untuk menyimpulkan bahwa kita perlu mengintip ke dalam longobject.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:

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

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:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

Jadi makro yang memanggil fungsi get_small_intjika nilainya ivalmemenuhi syarat:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

Jadi apa NSMALLNEGINTSdan apa NSMALLPOSINTS? Makro! Inilah mereka :

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

Jadi syarat kita adalah if (-5 <= ival && ival < 257)panggilan get_small_int.

Selanjutnya mari kita lihat get_small_intdalam semua kemuliaan (well, kita hanya akan melihat tubuhnya karena di situlah hal-hal yang menarik):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

Oke, nyatakan a PyObject, nyatakan bahwa kondisi sebelumnya memegang dan menjalankan tugas:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_intsterlihat sangat mirip dengan array yang telah kita cari, dan itu! Kita bisa saja membaca dokumentasi sialan itu dan kita akan tahu selama ini! :

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

Jadi ya, ini orang kita. Saat Anda ingin membuat yang baru intdalam 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 identitas isdengannya 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:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {

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:

>>> 257 is 257

Selama melengkapi pernyataan ini, CPython akan melihat bahwa Anda memiliki dua literal yang cocok dan akan menggunakan PyLongObjectrepresentasi yang sama 257. Anda dapat melihat ini jika Anda melakukan kompilasi sendiri dan memeriksa isinya:

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

Ketika CPython melakukan operasi, sekarang hanya akan memuat objek yang sama persis:

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

Jadi isakan kembali True.

Dimitris Fasarakis Hilliard
sumber
37

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.

malaikat
sumber
19

Saya pikir hipotesis Anda benar. Eksperimen dengan id(identitas objek):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

Tampaknya angka <= 255diperlakukan sebagai literal dan apa pun di atas diperlakukan berbeda!

Amit
sumber
1
Itu karena objek yang mewakili nilai dari -5 hingga +256 dibuat pada saat Startup - dan semua penggunaan nilai tersebut digunakan untuk objek yang dibuat ulang. Hampir semua referensi ke bilangan bulat di luar rentang itu membuat objek internal baru setiap kali direferensikan. Saya pikir penggunaan istilah literal membingungkan - literal biasanya merujuk pada nilai apa pun yang diketikkan pada sepotong kode - jadi semua angka dalam kode sumber adalah literal.
Tony Suffolk 66
13

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.

babbageclunk
sumber
12

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,, strdan bytes, dan beberapa string pendek (dalam CPython 3.6, ini adalah 256 karakter Latin-1 string tunggal). Sebagai contoh:

>>> a = ()
>>> b = ()
>>> a is b
True

Tetapi juga, bahkan nilai-nilai yang tidak dibuat sebelumnya bisa identik. Pertimbangkan contoh-contoh ini:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

Dan ini tidak terbatas pada intnilai:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

Jelas, CPython tidak datang dengan floatnilai yang dibuat sebelumnya untuk 42.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. Karena cdan ddidefinisikan dalam pernyataan terpisah, nilainya tidak digabungkan. Karena edan fdidefinisikan dalam pernyataan yang sama, nilainya digabungkan.


Anda dapat melihat apa yang terjadi dengan membongkar bytecode. Cobalah mendefinisikan fungsi yang berfungsi e, f = 128, 128dan memanggilnya dis.dis, dan Anda akan melihat bahwa ada nilai konstan tunggal(128, 128)

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

Anda mungkin memperhatikan bahwa kompiler telah disimpan 128sebagai 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:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

Menempatkan bahwa dalam fungsi, disitu, dan lihat di co_consts-ada 1dan 2, dua (1, 2)tupel yang berbagi sama 1dan 2tetapi 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:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

Di sisi lain, terbatas pada strjenisnya, 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:

  • Jangan menulis kode yang mengasumsikan dua nilai tidak berubah yang sama tetapi dibuat secara terpisah adalah identik (jangan gunakan x is y, gunakanx == y )
  • Jangan menulis kode yang mengasumsikan dua nilai tidak berubah sama tetapi dibuat secara terpisah adalah berbeda (jangan gunakan x is not y, gunakan x != y)

Atau, dengan kata lain, hanya digunakan isuntuk menguji lajang terdokumentasi (seperti None) atau yang hanya dibuat di satu tempat dalam kode (seperti _sentinel = object()idiom).

abarnert
sumber
Nasihat yang kurang samar sederhana: jangan gunakan x is yuntuk membandingkan, gunakan x == y. Demikian juga jangan gunakan x is not y, gunakanx != y
smci
Jadi melihat pertanyaan ini , mengapa ada a=257; b=257satu baris a is bBenar
Joe
8

is 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 dari isuntuk kelas yang mendefinisikan__eq__ dengan cara yang tidak masuk akal:

class Unequal:
    def __eq__(self, other):
        return False

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 BasicNow daripada menunjukkan bahwa ini adalah evaluasi dengan time.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 ==atau is. CPython mendefinisikan protokol angka , termasuk PyNumber_Check, tetapi ini tidak dapat diakses dari Python itu sendiri.

Kita bisa mencoba menggunakan isinstancedengan 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 dasar numbers.Number, tetapi memiliki masalah yang sama:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

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

Yann Vernier
sumber
4

Ini juga terjadi dengan string:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Sekarang semuanya tampak baik-baik saja.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Itu juga diharapkan.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

Nah, itu tidak terduga.

sobolevn
sumber
Terjadi pada ini - setuju, bahwa bahkan lebih aneh. Jadi saya bermain dengannya, dan lebih aneh - terkait dengan ruang. Misalnya, string 'xx'seperti yang diharapkan, sebagaimana adanya 'xxx', tetapi 'x x'tidak.
Brian
2
Itu karena terlihat seperti simbol jika tidak ada ruang di dalamnya. Nama-nama diinternir secara otomatis, jadi jika ada sesuatu yang bernama xxdi 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-interning
Yann Vernier
3

Apa yang Baru Di Python 3.8: Perubahan perilaku Python :

Compiler sekarang menghasilkan SyntaxWarning ketika pemeriksaan identitas ( isdan is not) digunakan dengan tipe literal tertentu (mis. String, ints). Ini sering dapat bekerja secara tidak sengaja di CPython, tetapi tidak dijamin oleh spesifikasi bahasa. Peringatan itu menyarankan pengguna untuk menggunakan tes kesetaraan ( == dan !=) sebagai gantinya.

cclauss
sumber