Domanda

Perché in Python si comportano in modo imprevisto?

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

Sto usando Python 2.5.2. Provando alcune versioni diverse di Python, sembra che Python 2.3.3 mostri il comportamento sopra tra 99 e 100.

Sulla base di quanto sopra, posso ipotizzare che Python sia implementato internamente in modo tale che "piccolo". i numeri interi sono memorizzati in modo diverso rispetto ai numeri più grandi e l'operatore is può dire la differenza. Perché l'astrazione che perde? Qual è un modo migliore di confrontare due oggetti arbitrari per vedere se sono uguali quando non so in anticipo se sono numeri o no?

È stato utile?

Soluzione

Dai un'occhiata a questo:

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

EDIT: ecco cosa ho trovato nella documentazione di Python 2, " Plain Integer Oggetti " (è lo stesso per Python 3 ):

  

L'attuale implementazione mantiene un   matrice di oggetti interi per tutti   numeri interi compresi tra -5 e 256, quando   crea un int in quell'intervallo tu   in realtà solo un riferimento a   l'oggetto esistente. Quindi dovrebbe essere   possibile modificare il valore di 1. I   sospettare il comportamento di Python in   questo caso non è definito. : -)

Altri suggerimenti

  

& # 8220; di Python è & # 8221; L'operatore si comporta in modo imprevisto con i numeri interi?

In sintesi, vorrei sottolineare: Non utilizzare è per confrontare numeri interi.

Questo non è un comportamento di cui dovresti avere aspettative.

Invece, usa == e ! = per confrontare rispettivamente per uguaglianza e disuguaglianza. Ad esempio:

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

Spiegazione

Per saperlo, devi sapere quanto segue.

In primo luogo, cosa fa è ? È un operatore di confronto. Dalla documentazione :

  

Gli operatori è e non è test per l'identità dell'oggetto: x is y è vero   se e solo se xey sono lo stesso oggetto. x is not y produce il valore   valore di verità inversa.

E quindi quanto segue è equivalente.

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

Dalla documentazione :

  

id   Restituisci l'identità & # 8220; & # 8221; di un oggetto. Questo è un numero intero (o lungo   intero) che è garantito per essere unico e costante per questo oggetto   durante la sua vita. Due oggetti con durata di vita non sovrapposta possono   hanno lo stesso valore id () .

Si noti che il fatto che l'id di un oggetto in CPython (l'implementazione di riferimento di Python) sia la posizione in memoria è un dettaglio dell'implementazione. Altre implementazioni di Python (come Jython o IronPython) potrebbero facilmente avere un'implementazione diversa per id .

Quindi qual è il caso d'uso per è ? PEP8 descrive :

  

I confronti con singoli come None devono sempre essere fatti con is o    non è , mai gli operatori di uguaglianza.

La domanda

Poni e dichiari la seguente domanda (con codice):

  

Perché in Python si comportano in modo imprevisto?

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

È non un risultato atteso. Perché è previsto? Significa solo che gli interi valutati in 256 a cui fanno riferimento sia a che b sono la stessa istanza di intero. I numeri interi sono immutabili in Python, quindi non possono cambiare. Ciò non dovrebbe avere alcun impatto su alcun codice. Non dovrebbe essere previsto. È solo un dettaglio di implementazione.

Ma forse dovremmo essere contenti che non ci sia una nuova istanza separata in memoria ogni volta che affermiamo che un valore è uguale a 256.

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

Sembra che ora abbiamo due istanze separate di numeri interi con il valore di 257 in memoria. Poiché i numeri interi sono immutabili, questo spreca memoria. Speriamo di non sprecare molto. Probabilmente no. Ma questo comportamento non è garantito.

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

Bene, sembra che la tua particolare implementazione di Python stia cercando di essere intelligente e non creare numeri interi ridondanti in memoria a meno che non sia necessario. Sembra che tu stia utilizzando l'implementazione referente di Python, che è CPython. Buono per CPython.

Potrebbe essere ancora meglio se CPython potesse farlo a livello globale, se potesse farlo in modo economico (come ci sarebbe un costo nella ricerca), forse un'altra implementazione potrebbe.

Ma per quanto riguarda l'impatto sul codice, non dovresti preoccuparti se un numero intero è un'istanza particolare di un numero intero. Dovresti preoccuparti solo del valore di quell'istanza e per questo useresti i normali operatori di confronto, ad esempio == .

Cosa significa

is verifica che id di due

Dipende se stai cercando di vedere se 2 cose sono uguali o lo stesso oggetto.

è verifica se sono lo stesso oggetto, non solo uguale. I piccoli ints stanno probabilmente puntando alla stessa posizione di memoria per l'efficienza dello spazio

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

Dovresti usare == per confrontare l'uguaglianza di oggetti arbitrari. Puoi specificare il comportamento con gli attributi __eq__ e __ne__ .

Sono in ritardo ma, vuoi una fonte con la tua risposta? *

La cosa buona di CPython è che puoi effettivamente vedere la fonte per questo. Per ora userò i collegamenti per la versione 3.5 ; trovare i 2.x corrispondenti è banale.

In CPython, la funzione C-API che gestisce la creazione di un nuovo oggetto int è PyLong_FromLong (long v) . La descrizione per questa funzione è:

  

L'attuale implementazione mantiene un array di oggetti interi per tutti i numeri interi compresi tra -5 e 256, quando si crea un int in quell'intervallo in realtà si ottiene semplicemente un riferimento all'oggetto esistente . Quindi dovrebbe essere possibile modificare il valore di 1. Sospetto che il comportamento di Python in questo caso non sia definito. : -)

Non ti conosco ma lo vedo e penso: Troviamo quell'array!

Se non hai armeggiato con il codice C che implementa CPython dovresti , tutto è abbastanza organizzato e leggibile. Nel nostro caso, dobbiamo cercare nella sottodirectory Objects / dell'albero albero delle directory del codice sorgente principale .

PyLong_FromLong si occupa di oggetti lunghi , quindi non dovrebbe essere difficile dedurre che dobbiamo sbirciare dentro longobject.c . Dopo aver guardato dentro potresti pensare che le cose siano caotiche; sono, ma non temono, la funzione che stiamo cercando è quella di rilassarsi a linea 230 ci aspetta per verificarlo. È una funzione piccola, quindi il corpo principale (escluse le dichiarazioni) è facilmente incollato qui:

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

Ora, non siamo C master-code-haxxorz ma non siamo nemmeno stupidi, possiamo vedere che CHECK_SMALL_INT (ival); ci dà una sbirciatina seducente; possiamo capire che ha qualcosa a che fare con questo. Diamo un'occhiata:

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

Quindi è una macro che chiama la funzione get_small_int se il valore ival soddisfa la condizione:

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

Quindi cosa sono NSMALLNEGINTS e NSMALLPOSINTS ? Se hai indovinato le macro non ottieni nulla perché non è stata una domanda così difficile. Comunque, eccoli qui :

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

Quindi la nostra condizione è if (-5 < = ival & amp; & amp; ival < 257) chiama get_small_int .

Nessun altro posto dove andare ma continua il nostro viaggio guardando get_small_int in tutto il suo splendore (beh, vedremo solo il suo corpo perché sono state le cose interessanti):

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

Va ??bene, dichiarare un PyObject , affermare che la condizione precedente è valida ed eseguire il compito:

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

small_ints assomiglia molto all'array che stavamo cercando .. ed è! Avremmo potuto leggere la dannata documentazione e avremmo lo sapete da sempre! :

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

Quindi sì, questo è il nostro ragazzo. Quando si desidera creare un nuovo int nell'intervallo [NSMALLNEGINTS, NSMALLPOSINTS) si ottiene semplicemente un riferimento a un oggetto già esistente che è stato preallocato.

Dal momento che il riferimento

Come puoi controllare file sorgente intobject.c , Python memorizza nella cache piccoli numeri interi per l'efficienza. Ogni volta che crei un riferimento a un numero intero piccolo, fai riferimento al numero intero piccolo memorizzato nella cache, non a un nuovo oggetto. 257 non è un numero intero piccolo, quindi viene calcolato come un oggetto diverso.

È meglio usare == a tale scopo.

Penso che le tue ipotesi siano corrette. Sperimenta con id (identità dell'oggetto):

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)

Sembra che i numeri < = 255 siano trattati come letterali e tutto quanto sopra sia trattato in modo diverso!

Per oggetti valore immutabili, come ints, stringhe o datetimes, l'identità dell'oggetto non è particolarmente utile. È meglio pensare all'uguaglianza. L'identità è essenzialmente un dettaglio di implementazione per oggetti valore - poiché sono immutabili, non c'è alcuna differenza effettiva tra avere più riferimenti allo stesso oggetto o più oggetti.

è è l'operatore di uguaglianza di identità (funzionando come id (a) == id (b) ); è solo che due numeri uguali non sono necessariamente lo stesso oggetto. Per motivi di prestazioni, alcuni piccoli numeri interi sembrano memoized , quindi tenderanno ad essere gli stessi (questo può essere fatto poiché sono immutabili).

l'operatore di PHP === , d'altra parte, è descritto come verifica dell'uguaglianza e del tipo: x == y e type (x) == type (y) come da commento di Paulo Freitas. Questo sarà sufficiente per i numeri comuni, ma differire da è per le classi che definiscono __eq__ in modo assurdo:

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

Sembra che PHP consenta la stessa cosa per " incorporato " classi (che presumo significhi implementate a livello C, non in PHP). Un uso leggermente meno assurdo potrebbe essere un oggetto timer, che ha un valore diverso ogni volta che viene usato come numero. È per questo che vorresti emulare il Now di Visual Basic invece di mostrare che si tratta di una valutazione con time.time () non lo so.

Greg Hewgill (OP) ha fatto un commento chiarificatore " Il mio obiettivo è confrontare l'identità dell'oggetto, piuttosto che l'uguaglianza di valore. Ad eccezione dei numeri, in cui desidero trattare l'identità dell'oggetto allo stesso modo della parità di valore. & Quot;

Questa avrebbe un'altra risposta, dato che dobbiamo classificare le cose come numeri o no, per scegliere se confrontare con == o è . CPython definisce protocollo numerico , incluso PyNumber_Check, ma questo non è accessibile da Python stesso.

Potremmo provare a usare isinstance con tutti i tipi di numero che conosciamo, ma ciò sarebbe inevitabilmente incompleto. Il modulo types contiene un elenco StringTypes ma nessun NumberTypes. A partire da Python 2.6, le classi numeriche incorporate hanno una classe base .Number , ma presenta lo stesso problema:

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

A proposito, NumPy produrrà istanze separate di numeri bassi.

In realtà non conosco una risposta a questa variante della domanda. Suppongo che in teoria si possano usare i ctypes per chiamare PyNumber_Check , ma anche quella funzione è stato discusso e certamente non è portatile. Dovremo solo essere meno particolari su ciò che testiamo per ora.

Alla fine, questo problema deriva da Python che in origine non aveva un albero dei tipi con predicati come Schema numero? o Haskell's type class Num . is controlla l'identità dell'oggetto, non l'uguaglianza di valore. Anche PHP ha una storia colorata, in cui === si comporta apparentemente come è solo sugli oggetti in PHP5, ma non in PHP4 . Tali sono i crescenti problemi di spostarsi tra le lingue (comprese le versioni di una).

C'è un altro problema che non è indicato in nessuna delle risposte esistenti. Python è autorizzato a unire due valori immutabili e i valori int piccoli predefiniti non sono l'unico modo in cui ciò può accadere. Un'implementazione di Python non è mai garantita per farlo, ma lo fanno tutti per qualcosa di più di piccoli ints.


Per prima cosa, ci sono alcuni altri valori pre-creati, come tuple , str e byte vuoti, e alcuni stringhe brevi (in CPython 3.6, sono le 256 stringhe latine-1 a carattere singolo). Ad esempio:

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

Ma anche i valori non pre-creati possono essere identici. Considera questi esempi:

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

E questo non si limita ai valori int :

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

Ovviamente, CPython non ha un valore float pre-creato per 42.23e100 . Quindi, cosa sta succedendo qui?

Il compilatore CPython unirà i valori costanti di alcuni tipi noti-immutabili come int , float , str , byte , nella stessa unità di compilazione. Per un modulo, l'intero modulo è un'unità di compilazione, ma all'interprete interattivo ogni istruzione è un'unità di compilazione separata. Poiché c e d sono definiti in istruzioni separate, i loro valori non vengono uniti. Poiché e e f sono definiti nella stessa istruzione, i loro valori vengono uniti.


Puoi vedere cosa succede smontando il bytecode. Prova a definire una funzione che fa e, f = 128, 128 e poi chiama dis.dis e vedrai che c'è un singolo valore costante (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

Potresti notare che il compilatore ha archiviato 128 come costante anche se non è effettivamente utilizzato dal bytecode, il che ti dà un'idea di quanto poca ottimizzazione compila il CPython. Ciò significa che le tuple (non vuote) in realtà non finiscono per essere unite:

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

Mettilo in una funzione, dis , e guarda il co_consts —c'è un 1 e un 2 , due (1, 2) che condividono gli stessi 1 e 2 ma non sono identici e un (( 1, 2), (1, 2)) tupla che ha le due tuple uguali distinte.


C'è un'altra ottimizzazione che CPython fa: interning delle stringhe. A differenza della piegatura costante del compilatore, questo non è limitato ai letterali del codice sorgente:

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

D'altra parte, è limitato al tipo str e alle stringhe di tipo di archiviazione interna" ascii compact "," quot "compatta" o "legacy ready" , e in molti casi solo "ascii compact" verrà internato.


Ad ogni modo, le regole per quali valori devono essere, potrebbero essere o non essere distinti variano da implementazione a implementazione e tra versioni della stessa implementazione e forse anche tra esecuzioni dello stesso codice sulla stessa copia di la stessa implementazione.

Vale la pena imparare le regole per un Python specifico per il gusto di farlo. Ma non vale la pena affidarsi a loro nel tuo codice. L'unica regola sicura è:

  • Non scrivere codice che assume due valori immutabili uguali ma creati separatamente sono identici.
  • Non scrivere codice che assume due valori immutabili uguali ma creati separatamente sono distinti.

O, in altre parole, utilizzare solo is per verificare i singleton documentati (come None ) o creati solo in un punto del codice (come il _sentinel = object () idiom).

Succede anche con le stringhe:

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

Ora tutto sembra a posto.

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

Anche questo è previsto.

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

Ora è inaspettato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top