2010-11-04 7 views
6

Czy ktoś mógłby mi wyjaśnić ten dziwny wynik na Pythona 2.6.6?Dziwny wynik w python

>>> a = "xx" 
>>> b = "xx" 
>>> a.__hash__() == b.__hash__() 
True 
>>> a is b 
True # ok.. was just to be sure 

>>> a = "x" * 2 
>>> b = "x" * 2 
>>> a.__hash__() == b.__hash__() 
True 
>>> a is b 
True # yeah.. looks ok so far ! 

>>> n = 2 
>>> a = "x" * n 
>>> b = "x" * n 
>>> a.__hash__() == b.__hash__() 
True # still okay.. 
>>> a is b 
False # hey! What the F... ? 
+5

Gdzie do cholery ludzie dowiadują się o "jest", ale nie o tym, jak to się różni od '=='? – delnan

+0

Możliwy duplikat [Python '==' vs 'is' porównywanie ciągów znaków 'czasami kończy się niepowodzeniem, dlaczego?] (Http://stackoverflow.com/questions/1504717/python-vs-is-comparing-strings-is -fails-czasami-dlaczego) – SilentGhost

+0

@SilentGhost: Niezupełnie, ponieważ dotyczy to tematu, kiedy kompilatory mogą niespodziewanie internować ciągi. –

Odpowiedz

12

Aby to zrozumieć, musisz zrozumieć kilka różnych rzeczy.

  • a is b Zwraca true jeśli a i bten sam obiekt, nie tylko jeśli mają taką samą wartość . Łańcuchy mogą mieć tę samą wartość, ale stanowią inne wystąpienie tej wartości.
  • Kiedy mówisz a = "x", to co właściwie robisz, to tworzenie stałej łańcuchowej "x", a następnie przypisanie jej nazwy, a. Ciąg stałe to ciągi, które są napisane dosłownie w kodzie i nie są obliczane programowo. Stałe ciągów są zawsze internowane, co oznacza, że ​​są przechowywane w tabeli do ponownego użycia: jeśli powiesz a = "a"; b = "a", to tak naprawdę jest to samo, co przy pisaniu a = "a"; b = a, ponieważ będą używać tego samego internowanego ciągu "a". Dlatego pierwszy a is b ma wartość True.
  • Kiedy mówisz a = "x" * 2, kompilator Pythona faktycznie to optymalizuje. Oblicza ciąg w czasie kompilacji - generuje kod tak, jakby napisał: a = "xx". Tak więc wynikowy ciąg "xx' jest internowany. Właśnie dlatego drugi a is b jest prawdziwy.
  • Kiedy mówisz: a = "x" * n, kompilator Python nie wie, co n jest podczas kompilacji. Dlatego jest zmuszony do wyprowadzenia ciągu znaków "x", a następnie wykonać mnożenie ciągu w czasie wykonywania.Ponieważ jest wykonywany w czasie wykonywania, podczas gdy "x" jest internowany, wynikowy ciąg "xx" jest , a nie. W wyniku tego każdy z tych łańcuchów jest inny niż instancja "xx", więc ostateczna wersja to False.

Widać różnicę siebie:

def a1(): 
    a = "x" 
def a2(): 
    a = "x" * 2 
def a3(): 
    n = 2 
    a = "x" * n 


import dis 
print "a1:" 
dis.dis(a1) 

print "a2:" 
dis.dis(a2) 

print "a3:" 
dis.dis(a3) 

W CPython 2.6.4, to wyjść:

a1: 
    4   0 LOAD_CONST    1 ('x') 
       3 STORE_FAST    0 (a) 
       6 LOAD_CONST    0 (None) 
       9 RETURN_VALUE 
a2: 
    6   0 LOAD_CONST    3 ('xx') 
       3 STORE_FAST    0 (a) 
       6 LOAD_CONST    0 (None) 
       9 RETURN_VALUE 
a3: 
    8   0 LOAD_CONST    1 (2) 
       3 STORE_FAST    0 (n) 

    9   6 LOAD_CONST    2 ('x') 
       9 LOAD_FAST    0 (n) 
      12 BINARY_MULTIPLY 
      13 STORE_FAST    1 (a) 
      16 LOAD_CONST    0 (None) 
      19 RETURN_VALUE 

Wreszcie, należy pamiętać, że można powiedzieć a = intern(a); b = intern(b) stworzyć internowanych wersje jeśli ciągi znaków, które zagwarantują, że a is b jest prawdziwe. Jeśli jednak chcesz tylko sprawdzić równość łańcuchów, użyj po prostu a == b.

+0

Wznowienie dla samej kompleksowości – kindall

+0

O stażystce, znalazłem ciągi mniejsze lub równe niż 20 znaków długości są automatycznie internowane przez Python Moje prawdziwe pytanie brzmi, dlaczego "x" * 2 (internowany) było inne niż "x" * n .. (nie internowany, nawet jeśli n <= 20) – pyrou

17

Operator is powie Ci czy dwóch zmiennych punktowych do tego samego obiektu w pamięci. Jest rzadko przydatny i często mylony z operatorem ==, który mówi, czy dwa obiekty "wyglądają tak samo".

Jest to szczególnie mylące, gdy używa się go z krótkimi literałami literowymi, ponieważ kompilator Pythona umieszcza je w celu zwiększenia wydajności. Innymi słowy, podczas pisania "xx" kompilator (emituje kod bajtowy) tworzy jeden obiekt typu string w pamięci i powoduje, że wszystkie literały "xx" wskazują na to. To tłumaczy, dlaczego twoje pierwsze dwa porównania są prawdziwe. Zauważ, że można uzyskać identyfikator strun poprzez wywołanie id na nich, które (przynajmniej na CPython jest prawdopodobnie) ich adresów w pamięci:

>>> a = "xx" 
>>> b = "xx" 
>>> id(a) 
38646080 
>>> id(b) 
38646080 
>>> a is b 
True 
>>> a = "x"*10000 
>>> b = "x"*10000 
>>> id(a) 
38938560 
>>> id(b) 
38993504 
>>> a is b 
False 

Trzeci jest, ponieważ kompilator nie został internowany struny a i b, z dowolnego powodu (prawdopodobnie dlatego, że nie jest wystarczająco inteligentny, aby zauważyć, że zmienna n jest zdefiniowana raz, a następnie nigdy nie jest modyfikowana).

W rzeczywistości można wymusić Python na strunach przez, no, asking it to. Zapewni to zwiększenie wydajności i może pomóc. To prawdopodobnie bezużyteczne.

Moralne: nie używaj is z literałami ciągów. Lub int literały. Albo gdziekolwiek nie masz na myśli tego, naprawdę.

+0

Warto byłoby dodać wyjaśnienie, dlaczego pierwsze dwa 'a jest b' wywołuje pracę. –

+0

A jeśli chcesz dodatkowo sprawdzić, przetestuj wyniki "id (a)" i "id (b)" dla trzech przypadków. – user470379

+0

* z dowolnego powodu *. a powód jest dość oczywisty, prawda? ponieważ kompilator nie generuje kodu bajtowego dla tych n-długich łańcuchów. – SilentGhost