"İs" operatörü tamsayılarla beklenmedik şekilde davranıyor


509

Python'da aşağıdakiler neden beklenmedik davranıyor?

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

Python 2.5.2 kullanıyorum. Python'un bazı farklı sürümlerini denerken, Python 2.3.3'ün 99 ve 100 arasındaki yukarıdaki davranışı gösterdiği görülmektedir.

Yukarıdakilere dayanarak, Python'un "küçük" tamsayıların daha büyük tamsayılardan farklı bir şekilde saklanacağı ve isoperatörün farkı söyleyebileceği şekilde dahili olarak uygulandığını varsayabilirim . Neden sızdıran soyutlama? Sayı olup olmadığını önceden bilmediğimde, aynı olup olmadıklarını görmek için iki keyfi nesneyi karşılaştırmanın daha iyi bir yolu nedir?


1
Buraya bir göz atın > Geçerli uygulama, bir dizi tamsayı nesnesi tutar> -5 ve 256 arasında tamsayılar, o aralıkta bir int oluşturduğunuzda> aslında sadece mevcut nesneye bir referans alırsınız.
user5319825

2
Bu bir CPython'a özgü uygulama detayı ve tanımlanmamış bir davranış,
dikkatle

Yanıtlar:


392

Şuna bir bak:

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

İşte "Düz Tamsayı Nesneleri" Python 2 belgelerinde buldum ( Python 3 için aynıdır ):

Geçerli uygulama, -5 ile 256 arasındaki tüm tamsayılar için bir tamsayı nesnesi dizisi tutar, bu aralıkta bir int oluşturduğunuzda, aslında varolan nesneye bir başvuru alırsınız. Bu nedenle 1 değerini değiştirmek mümkün olmalıdır. Bu durumda Python'un davranışının tanımsız olduğundan şüpheleniyorum. :-)


46
bu aralığın (-5, 256) nasıl seçildiğini bilen var mı? (0, 255) veya hatta (-255, 255) olsaydı çok şaşırmazdım, ancak -5'ten başlayan 262 sayı aralığı şaşırtıcı derecede keyfi görünüyor.
Woodrow Barlow

6
@WoodrowBarlow: -5 ortak negatif yer tutucuları yakalamak için sadece bir sezgisel tarama. 0..255 tek baytlık değerlerin dizilerini kapsar. Bu gizemli bir 256, ama sanırım tamsayıyı baytlara / baytlardan birleştirmek için.
Davis Herring

3
Anladığım kadarıyla, aralık birden fazla proje (ve birden çok dil) arasında yaygın olarak kullanılan değerlere bakarak seçildi.
Tony Suffolk 66

9
Göre reddit.com/r/Python/comments/18leav/... , aralık [-5100] olması için kullanılır. Tüm bayt değerlerini - artı 256'yı içerecek şekilde genişletildi, çünkü bu muhtemelen ortak bir sayıdır.
mwfearnley

2
@Ashwani, sizinkinden iki yıl önce yayınlanan yorumunuzun hemen yanındaki yorumları okumayı deneyin ve sorunuzun yanıtını bulacaksınız.
jbg

116

Python'un “is” operatörü tamsayılarla beklenmedik bir şekilde davranıyor mu?

Özetle - vurgulayayım: Tamsayıları karşılaştırmak için kullanmayın is.

Bu, herhangi bir beklentiniz olması gereken davranış değildir.

Bunun yerine, kullanımı ==ve !=sırasıyla, eşitlik ve eşitsizlik için karşılaştırmak. Örneğin:

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

açıklama

Bunu bilmek için aşağıdakileri bilmeniz gerekir.

İlk olarak, ne isişe yarar? Bir karşılaştırma operatörüdür. Gönderen belgeler :

İşleçler isve is notnesne kimliği testi: x is yyalnızca x ve y aynı nesne ise doğrudur. x is not yters doğruluk değeri verir.

Ve böylece aşağıdakiler eşdeğerdir.

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

Gönderen belgeler :

id Bir nesnenin “kimliğini” döndür. Bu, ömrü boyunca bu nesne için benzersiz ve sabit olduğu garanti edilen bir tam sayıdır (veya uzun tam sayıdır). Örtüşmeyen ömürleri olan iki nesne aynı id()değere sahip olabilir .

CPython'daki bir nesnenin kimliğinin (Python'un referans uygulaması) bellekteki konum olduğuna dikkat edin. Python'un diğer uygulamaları (Jython veya IronPython gibi) için kolayca farklı bir uygulamaya sahip olabilir id.

Peki kullanım durumu ne işe yarıyor is? PEP8 şunları açıklar :

Gibi tektonlarla karşılaştırmalar Nonedaima eşitlik operatörleri ile isveya is notasla yapılmamalıdır .

Soru

Aşağıdaki soruyu soruyorsunuz ve belirtiyorsunuz (kodlu):

Python'da aşağıdakiler neden beklenmedik davranıyor?

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

Öyle değil , beklenen bir sonuçtur. Neden bekleniyor? Sadece değerinde tamsayılar anlamına gelir 256ikisi tarafından başvurulan aveb tamsayı ile aynı . Tamsayılar Python'da değişmez, bu yüzden değişemezler. Bunun herhangi bir kod üzerinde etkisi olmamalıdır. Beklenmemeli. Bu sadece bir uygulama detayıdır.

Ama belki de bir değerin 256'ya eşit olduğunu her ifade ettiğimizde bellekte yeni bir ayrı örnek olmadığından memnun olmalıyız.

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

Görünüşe göre artık 257bellekte değeri olan iki ayrı tamsayı örneğimiz var . Tamsayı değişmez olduğu için, bu hafıza israfına neden olur. Umarım çok fazla israf etmeyiz. Muhtemelen değiliz. Ancak bu davranış garanti edilmez.

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

Python'un özel uygulamanız akıllı olmaya çalışıyor ve gerekmedikçe bellekte yedekli tamsayılar oluşturmuyor gibi görünüyor. CPython olan Python'un başvuru uygulamasını kullandığınızı belirtiyorsunuz. CPython için iyi.

CPython'un bunu küresel olarak yapabilmesi, daha ucuza yapabilmesi (aramada bir maliyet olacağı gibi) daha iyi olabilir, belki başka bir uygulama olabilir.

Ancak kod üzerindeki etki konusunda, bir tamsayının belirli bir tamsayı örneği olup olmadığına dikkat etmemelisiniz. Yalnızca bu örneğin değerinin ne olduğunu önemsemelisiniz ve bunun için normal karşılaştırma işleçlerini kullanırsınız, yani ==.

Ne isyapar

isidiki nesnenin aynı olup olmadığını kontrol eder . CPython'da, idbellekteki konumdur, ancak başka bir uygulamada benzersiz bir şekilde tanımlayan başka bir sayı olabilir. Bunu kodla yeniden başlatmak için:

>>> a is b

aynıdır

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

Neden kullanmak istiyoruz is?

Bu, çok uzun iki dizenin değerin eşit olup olmadığını kontrol etmek için çok hızlı bir kontrol olabilir. Ancak, nesnenin benzersizliği için geçerli olduğundan, bunun için sınırlı kullanım durumlarımız vardır. Aslında, bunu çoğunlukla Nonetek bir (bellekte bir yerde bulunan tek bir örnek) olan kontrol etmek için kullanmak istiyoruz . Eğer onları kontrol edebileceğimiz is, eğer onları sınırlandırabilme potansiyeli varsa, diğer tekiltonlar yaratabiliriz , ancak bunlar nispeten nadirdir. İşte bir örnek (Python 2 ve 3'te çalışacak) örn.

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()

Hangi baskılar:

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

Ve böylece, isbir nöbetçi ile ve bir nöbetçi ile görüyoruz, ne zaman barargüman olmadan çağrıldığımız ile çağrıldığımız zaman arasında ayrım yapabiliyoruz None. Bunlar için birincil kullanım-durumlardır is- do not bunlar gibi tamsayılar, dizeleri, küpe veya diğer şeylerin eşitliği için teste için kullanabilirsiniz.


"Bunlar birincil kullanım durumlarıdır is- tamsayıların, dizelerin, tupllerin veya bunun gibi diğer şeylerin eşitliğini test etmek için kullanmayın." Bununla birlikte, basit bir durum makinesini sınıfıma entegre etmeye çalışıyorum ve devletler, sadece gözlemlenebilir özelliği aynı veya farklı olma olan opak değerler olduğundan, karşılaştırılabilir olmaları oldukça doğal görünüyor is. Stajyer dizeleri devlet olarak kullanmayı planlıyorum. Düz tamsayıları tercih ederdim, ama ne yazık ki Python stajyer tamsayıları olamaz ( 0 is 0bir uygulama detayıdır).
Alexey

@Alexey enum'lara ihtiyacın var gibi mi geliyor? stackoverflow.com/questions/37601644/…
Aaron Hall

Belki, teşekkürler, onları bilmiyordum. Bu, IMO'ya cevap vermeniz için uygun bir ek olabilir.
Alexey

Belki cevabınızda sentinel gibi bir dizi aptal nesne kullanmak daha hafif bir çözüm olacaktır ...
Alexey

@Alexey enumlar Python 3 standart kitaplığındadır ve muhtemelen kodunuzun çıplak sentinellerden biraz daha anlamlı olmasını teşvik eder.
Aaron Hall

60

Bu, 2 şeyin eşit olup olmadığını veya aynı nesneyi görmek isteyip istemediğinize bağlıdır.

issadece eşit değil, aynı nesne olup olmadığını kontrol eder. Küçük ints, alan verimliliği için muhtemelen aynı bellek konumuna işaret ediyor

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

==Rasgele nesnelerin eşitliğini karşılaştırmak için kullanmalısınız . Davranışı __eq__, ve __ne__öznitelikleriyle belirleyebilirsiniz.


OP gibi rastgele nesnelerin nasıl karşılaştırılacağını açıkladığı için Yaşasın !!
Joooeey

54

Geç kaldım ama cevabınızla ilgili bir kaynak mı istiyorsunuz? Daha fazla insanın takip edebilmesi için bunu tanıtıcı bir şekilde söyleyeceğim.


CPython ile ilgili iyi bir şey, bunun kaynağını gerçekten görebilmenizdir. 3.5 sürümü için bağlantılar kullanacağım , ancak karşılık gelen 2.x olanları bulmak önemsiz.

CPython'da, yeni bir nesne oluşturmayı işleyen C-API işlevi vardır . Bu işlevin açıklaması:intPyLong_FromLong(long v)

Geçerli uygulama, -5 ve 256 arasındaki tüm tamsayılar için bir tamsayı nesnesi dizisi tutar, bu aralıkta bir int oluşturduğunuzda, aslında varolan nesneye bir başvuru alırsınız . Bu nedenle 1 değerini değiştirmek mümkün olmalıdır. Bu durumda Python'un davranışının tanımsız olduğundan şüpheleniyorum. :-)

(İtalik harflerle)

Seni bilmiyorum ama bunu görüyorum ve düşünüyorum: Hadi o diziyi bulalım!

Eğer CPython uygulayan C kodu ile fiddled olmadıysanız yapmanız gerekir ; her şey oldukça organize ve okunabilir. Bizim durumumuzda için, içinde bakmak gerekir Objectsalt dizinine ait ana kaynak kodu dizin ağacında .

PyLong_FromLonglongnesnelerle ilgilenir, bu yüzden içeriye bakmamız gerektiğine karar vermek zor olmamalıdır longobject.c. İçeri baktıktan sonra işlerin kaotik olduğunu düşünebilirsiniz; aradığımız fonksiyon, 230. satırda ürpertici olup olmadığını kontrol etmemizi bekliyor. Bu ufacık bir işlevdir, bu nedenle ana gövde (beyanlar hariç) buraya kolayca yapıştırılır:

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; 
}

Şimdi biz C master-kodu-haxxorz değiliz ama biz de aptal değiliz CHECK_SMALL_INT(ival);, hepimize baştan çıkarıcı bakmayı görüyoruz ; bununla bir ilgisi olduğunu anlayabiliriz. Hadi kontrol edelim:

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

Bu get_small_int, değer ivalkoşulu karşılarsa işlevi çağıran bir makrodur :

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

Peki ne NSMALLNEGINTSve NSMALLPOSINTS? Makro! İşte bunlar :

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

Bizim durumumuz if (-5 <= ival && ival < 257)çağrıget_small_int .

Sonra get_small_inttüm ihtişamıyla bakalım (iyi, sadece vücuduna bakacağız çünkü ilginç şeylerin olduğu yer):

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

Tamam, bir PyObjectönceki durumun geçerli olduğunu iddia edin ve ödevi yürütün:

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

small_intsaradığımız diziye çok benziyor ve öyle! Lanet olası belgeleri okuyabilirdik ve her şeyi bilirdik! :

/* 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];

Evet, bu bizim adamımız. intAralıkta yeni oluşturmak istediğinizde[NSMALLNEGINTS, NSMALLPOSINTS) önceden mevcut olan mevcut bir nesneye bir referans geri alırsınız.

Referans aynı nesneyi ifade ettiğinden, id()doğrudan veren veyais ettiğinden, üzerinde tamamen aynı şeyi döndürür.

Ancak, ne zaman tahsis edilirler?

_PyLong_InitPython başlatma sırasında memnuniyetle bir for döngüsü girecektir bunu sizin için yapın:

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

Döngü gövdesini okumak için kaynağı kontrol edin!

Umarım açıklamam sana şimdi C şeylerini net bir şekilde yaptı (pun açıkça belli).


Fakat, 257 is 257 ? Naber?

Bunu açıklamak daha kolay ve ben bunu zaten yapmaya çalıştım ; Python'un bu etkileşimli ifadeyi tek bir blok olarak yürütmesi nedeniyle:

>>> 257 is 257

Bu ifadenin derlenmesi sırasında, CPython eşleşen iki değişmeziniz olduğunu görecek ve aynı PyLongObjecttemsili kullanacaktır 257. Derlemeyi kendiniz yapar ve içeriğini incelerseniz bunu görebilirsiniz:

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

CPython işlemi gerçekleştirdiğinde, şimdi sadece aynı nesneyi yükleyecek:

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

Böylece isdönecektir True.


37

Kaynak dosyasına intobject.c dosyasını kontrol edebileceğiniz gibi , Python verimlilik için küçük tam sayıları önbelleğe alır. Küçük bir tamsayıya her başvuru oluşturduğunuzda, yeni bir nesneye değil, önbelleğe alınmış küçük tamsayıya başvurursunuz. 257 küçük bir tam sayı değildir, dolayısıyla farklı bir nesne olarak hesaplanır.

Bu ==amaçla kullanmak daha iyidir .


19

Bence hipotezleriniz doğru. Deneme id(nesnenin kimliği):

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)

Sayılar anlaşılmaktadır <= 255muamelesi görür ve yukarıdaki şey farklı muamele edilir!


1
Bunun nedeni, -5 ile +256 arasındaki değerleri temsil eden nesnelerin Başlangıç ​​zamanında oluşturulmasıdır ve bu nedenle bu değerin tüm kullanımı önceden oluşturulmuş nesneyi kullanır. Bu aralığın dışındaki tamsayılara yapılan neredeyse tüm referanslar, her başvuruda bulunulduğunda yeni bir dahili nesne oluşturur. Kaynak terim kullanımı kafa karıştırıcı olduğunu düşünüyorum - değişmez normalde bir kod parçasına yazılan herhangi bir değeri ifade eder - bu nedenle kaynak kodundaki tüm sayılar değişmezdir.
Tony Suffolk 66

13

Ints, dize veya datIME gibi değişmez değerli nesneler için nesne kimliği özellikle yararlı değildir. Eşitliği düşünmek daha iyidir. Kimlik aslında değer nesneleri için bir uygulama detayıdır - değişmez olduklarından, aynı nesneye veya birden fazla nesneye birden fazla başvuru yapmak arasında etkili bir fark yoktur.


12

Mevcut cevapların hiçbirinde belirtilmeyen başka bir sorun var. Python'un iki değişmez değeri birleştirmesine izin verilir ve önceden oluşturulmuş küçük int değerleri bunun olabileceği tek yol değildir. Bir Python uygulaması bunu yapmak için asla garanti edilmez , ancak hepsi bunu küçük ints'tan daha fazlası için yapar.


Bir kere, böyle boş gibi diğer bazı önceden oluşturulmuş değerler vardır tuple, strve bytes, ve bazı kısa dizeleri (CPython 3.6, bu 256 tek karakterlik Latin-1 dizeleri var). Örneğin:

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

Ancak, önceden oluşturulmamış değerler bile aynı olabilir. Şu örnekleri düşünün:

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

Ve bu sadece intdeğerlerle sınırlı değil :

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

Açıkçası, CPython için önceden oluşturulmuş bir floatdeğerle gelmiyor 42.23e100. Peki, burada neler oluyor?

CPython derleyici gibi bazı bilinen-Değiştirilemez tiplerin sabit değerleri birleştirmek olacaktır int, float, str, bytes, aynı derleme birimine. Bir modül için, tüm modül bir derleme birimidir, ancak etkileşimli yorumlayıcıda her ifade ayrı bir derleme birimidir. Yana cve dayrı açıklamalarda tanımlanan, değerleri birleştirilmez. Yana eve faynı açıklamada tanımlanmıştır, bunların değerleri birleştirilir.


Bayt kodunu sökerek neler olup bittiğini görebilirsiniz. Yapan e, f = 128, 128ve sonra çağıran bir işlev tanımlamayı deneyin dis.dis, böylece tek bir sabit değer olduğunu göreceksiniz.(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

Derleyicinin 128aslında bytecode tarafından kullanılmamasına rağmen sabit olarak depolandığını fark edebilirsiniz , bu da CPython'un derleyicisinin ne kadar az optimizasyon yaptığı hakkında bir fikir verir. Bu, (boş olmayan) tupl'lerin aslında birleştirilmediği anlamına gelir:

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

Bunu bir fonksiyona koyun disve bakın co_consts- a 1ve a 2, (1, 2)aynı olan 1ve 2özdeş olmayan ((1, 2), (1, 2))iki tuple ve iki farklı eşit tuplaya sahip bir tuple.


CPython'un yaptığı bir optimizasyon daha var: string interning. Derleyici sabit katlamasının aksine, bu kaynak kodu değişmezleriyle sınırlı değildir:

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

Öte yandan, "ascii compact", "compact" veya "legacy ready" gibi dahili depolama türüstr ve dizeleri ile sınırlıdır ve birçok durumda yalnızca "ascii compact" stajyer olur.


Her halükarda, hangi değerlerin olması gerektiğine, farklı olabileceğine veya farklı olamayacağına ilişkin kurallar uygulamadan uygulamaya ve aynı uygulamanın sürümleri arasında ve hatta aynı uygulamanın aynı kopyası üzerindeki aynı kodun çalıştırılması arasında değişebilir. .

Eğlenmek için belirli bir Python için kuralları öğrenmeye değer olabilir. Ancak kodunuzda onlara güvenmeye değmez. Tek güvenli kural:

  • İki eşit fakat ayrı ayrı oluşturulmuş değişmez değerin aynı olduğunu varsayan kod yazmayın (kullanmayın x is y, kullanmayın x == y)
  • İki eşit fakat ayrı ayrı oluşturulmuş değişmez değerin farklı olduğunu kabul eden kod yazmayın (kullanma x is not y, kullanma x != y)

Başka bir deyişle, yalnızca isbelgelenmiş tekilleri (örneğin None) test etmek için kullanın veya kodda yalnızca bir yerde ( _sentinel = object()deyim gibi ) oluşturulanlar.


Daha az şifreli tavsiye basitçe: x is ykarşılaştırmak için kullanmayın, kullanın x == y. Aynı şekilde kullanmayın x is not y, kullanınx != y
smci

Bu soruya baktığımızda , neden a=257; b=257tek bir satırda a is bTrue
Joe

8

is olan kimlik Eşitlik operatörü (işlevi gören id(a) == id(b)); iki eşit sayının aynı nesne olması gerekmez. Performans nedenlerinden dolayı, bazı küçük tamsayılar hafızaya alınır, böylece aynı olma eğilimindedirler (bu değişmez oldukları için yapılabilir).

PHP === operatörü ise eşitliği ve türü kontrol etmek olarak tanımlanır: x == y and type(x) == type(y)Paulo Freitas'nin yorumuna göre. Bu, yaygın sayılar için yeterli olacaktır, ancak saçma bir şekilde istanımlayan sınıflardan farklı olacaktır __eq__:

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

PHP görünüşe göre "yerleşik" sınıflar için aynı şeyi sağlar (ki PHP değil, C düzeyinde uygulandığı anlamına gelir). Biraz daha saçma bir kullanım, sayı olarak her kullanıldığında farklı bir değere sahip bir zamanlayıcı nesnesi olabilir. Neden bilmiyorum Nowile bir değerlendirme olduğunu göstermek yerine Visual Basic's taklit etmek istersiniz time.time().

Greg Hewgill (OP) açıklayıcı bir yorum yaptı: "Amacım değer eşitliğinden ziyade nesne kimliğini karşılaştırmak. Sayılar hariç, nesne kimliğine değer eşitliğiyle aynı davranmak istiyorum."

Biz ile karşılaştırmak belirlemek için, sayılar ya da değil gibi şeyleri kategorize etmek gerekiyor bu, henüz başka bir yanıt olması için ==ya is. CPython , PyNumber_Check dahil sayı protokolünü tanımlar , ancak buna Python'un kendisinden erişilemez.

isinstanceBildiğimiz tüm sayı türleriyle kullanmaya çalışabiliriz , ancak bu kaçınılmaz olarak eksik olacaktır. Types modülü bir StringTypes listesi içerir ancak NumberTypes içermez. Python 2.6'dan bu yana, yerleşik sayı sınıflarının temel sınıfı vardır numbers.Number, ancak aynı sorunu vardır:

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

Bu arada, NumPy düşük sayıların ayrı örneklerini üretecektir.

Aslında sorunun bu varyantına bir cevap bilmiyorum. Birisinin teorik olarak ctypes'i aramak için kullanabileceğini düşünüyorum PyNumber_Check, ancak bu fonksiyon bile tartışıldı ve kesinlikle taşınabilir değil. Şimdilik neyi test ettiğimiz konusunda daha az net olmalıyız.

Sonunda, bu sorun Python'un aslında Şema number? veya Haskell'in tip sınıfı Num gibi tahminleri olan bir tip ağacına sahip olmamasından kaynaklanmaktadır . isdeğer eşitliğini değil nesne kimliğini kontrol eder. PHP'nin de ===görünüşe göre issadece PHP5'teki nesnelerde olduğu gibi PHP4'te olmadığı gibi renkli bir geçmişi vardır . Bunlar, diller arasında hareket etmenin artan acılarıdır (bir versiyonları dahil).


4

Dizelerle de olur:

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

Şimdi her şey yolunda görünüyor.

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

Bu da bekleniyor.

>>> 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)

Şimdi bu beklenmedik bir şey.


Bunun üzerine oldu - kabul etti, hatta garip. Bu yüzden onunla oynadım ve henüz tuhaf - uzayla ilgili. Örneğin, dize 'xx'beklendiği gibi 'xxx', ancak 'x x'değil.
Brian

2
Çünkü içinde boşluk yoksa bir sembol gibi görünüyor. İsimler otomatik olarak stajyer olduğundan xx, Python oturumunuzda herhangi bir yerde adlandırılmış bir şey varsa , o dize zaten stajyerdir; ve sadece bir isme benziyorsa bunu yapan bir buluşsal yöntem olabilir. Rakamlarda olduğu gibi, bu değişmez oldukları için yapılabilir. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interning
Yann Vernier

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.