2012-07-23 4 views
8

Ho cercato nel Web e ho accumulato domande di overflow ma non sono riuscito a trovare una risposta a questa domanda. L'osservazione che ho fatto è che in Python 2.7.3, se assegni a due variabili la stessa stringa di carattere singolo, ad es.In quali circostanze le stringhe uguali condividono lo stesso riferimento?

>>> a = 'a' 
>>> b = 'a' 
>>> c = ' ' 
>>> d = ' ' 

Poi le variabili si condividono la stessa di riferimento:

>>> a is b 
True 
>>> c is d 
True 

Questo vale anche per alcune stringhe più lunghe:

>>> a = 'abc' 
>>> b = 'abc' 
>>> a is b 
True 
>>> ' ' is ' ' 
True 
>>> ' ' * 1 is ' ' * 1 
True 

Tuttavia, ci sono un sacco di casi in cui il riferimento è (non inaspettatamente) non condiviso:

>>> a = 'a c' 
>>> b = 'a c' 
>>> a is b 
False 
>>> c = ' ' 
>>> d = ' ' 
>>> c is d 
False 
>>> ' ' * 2 is ' ' * 2 
False 

Qualcuno può spiegare la ragione di ciò?

Sospetto che ci possano essere delle semplificazioni/sostituzioni eseguite dall'interprete e/o un meccanismo di memorizzazione nella cache che faccia uso del fatto che le stringhe Python sono immutabili da ottimizzare in alcuni casi speciali, ma che ne so? Ho provato a fare delle copie profonde delle stringhe usando il costruttore str e la funzione copy.deepcopy ma le stringhe condividono ancora i riferimenti in modo incoerente.

Il motivo per cui ho problemi con questo è perché controllo la disuguaglianza dei riferimenti alle stringhe in alcuni test di unità che sto scrivendo per metodi clone di classi python di nuovo stile.

risposta

8

I dettagli di quando le stringhe vengono memorizzate nella cache e riutilizzate dipendono dall'implementazione, possono passare dalla versione di Python alla versione di Python e non possono essere considerate affidabili. Se si desidera verificare le stringhe per l'uguaglianza, utilizzare ==, non is.

In CPython (l'implementazione Python più comunemente utilizzata), i valori letterali stringa che si verificano nel codice sorgente sono sempre internati, quindi se la stessa stringa letterale si verifica due volte nel codice sorgente, finiranno per puntare alla stessa stringa oggetto. In Python 2.x, è anche possibile chiamare la funzione integrata intern() per forzare l'internamento di una determinata stringa, ma in realtà non si dovrebbe farlo.

Modificare per quanto riguarda l'obiettivo effettivo di controllare se gli attributi sono condivisi in modo non corretto tra istanze: questo tipo di controllo è utile solo per oggetti mutabili. Per gli attributi di tipo immutabile, non esiste alcuna differenza semantica tra oggetti condivisi e non condivisi. Si potrebbe escludere i tipi immutabili da test utilizzando

Immutable = basestring, tuple, numbers.Number, frozenset 
# ... 
if not isinstance(x, Immutable): # Exclude types known to be immutable 

Si noti che questo escluderebbe anche tuple che contengono oggetti mutabili. Se si desidera testarli, è necessario scendere ricorsivamente in tuple.

+0

Grazie per la risposta, sembra che lei ha ragione in merito a tale stringhe in codice sorgente sono sempre internati (stringhe che non sono letterali non hanno questa proprietà). Quindi, i miei esempi sono validi solo quando si scrive nell'interprete interattivo python. Sto usando CPython e la tua risposta conferma alcuni dei miei sospetti. Sono d'accordo che l'operatore == dovrebbe essere usato per controllare le stringhe per l'uguaglianza. C'è un modo elegante per controllare dove è stato inizializzato un attributo o dovrei semplicemente evitare questo tipo di test? – EriF89

+0

@ EriF89: cosa significa controllare "dove è stato inizializzato un attributo"? E come importa? –

+0

Dato che sto scrivendo dei test per assicurarmi che i riferimenti agli attributi non siano condivisi in modo improprio tra gli oggetti clonati, vorrei un modo generico per verificare che le modifiche in uno degli oggetti non trapelino nell'altra. Ho pensato che l'operatore is avrebbe fatto questo, ma dal momento che i riferimenti sono condivisi, suppongo di dover assegnare nuovi valori agli attributi di uno degli oggetti, e controllare che non siano cambiati nell'altro, invece. – EriF89

4

Penso che sia una cosa di implementazione e ottimizzazione. Se la stringa è breve, possono (e sono spesso?) "Condivisi", ma non puoi dipendere da quello. Una volta che hai stringhe più lunghe, puoi vedere che non sono la stessa cosa.

In [2]: s1 = 'abc' 
In [3]: s2 = 'abc' 

In [4]: s1 is s2 
Out[4]: True 

stringhe più lunghe

In [5]: s1 = 'abc this is much longer' 
In [6]: s2 = 'abc this is much longer' 

In [7]: s1 is s2 
Out[7]: False 

uso == per confrontare le stringhe (e non l'operatore is).

-

PO osservazione/ipotesi (nei commenti qui sotto) che puo 'essere dovuto al numero di gettoni sembra essere supportato dal seguente:

In [12]: s1 = 'a b c' 
In [13]: s2 = 'a b c' 

In [14]: s1 is s2 
Out[14]: False 

se confrontato con l'iniziale esempio di abc sopra.

+0

A me, sembra che il numero di gettoni (sequenze di caratteri alfanumerici separate da stringhe o altri simboli) nelle materie di stringa: In [1] s1 = 'abcthisismuchlonger' In [2] s2 = 'abcthisismuchlonger' In [3] s1 è s2 Out [3] True Se c'è più di un token, la stringa non è condivisa. – EriF89

+0

@ EriF89 E 'possibile, non ho mai veramente esaminato i dettagli di come questo è implementato, cerco solo di ricordare di usare '==' quando si confrontano le stringhe e non dipendono dallo spazio di archiviazione tra stringhe "identiche" condivise. La tua ipotesi su come questo è deciso ha senso, ho appena provato con ''a b c'' ed è uscito come' False'. – Levon

5

In CPython, come dettaglio di implementazione the empty string is shared, come le stringhe di carattere singolo il cui punto di codice si trova nell'intervallo Latin-1. Si dovrebbe non dipendere da questo, in quanto è possibile ignorare questa funzione.

È possibile richiedere una stringa per internata utilizzando sys.intern; in alcuni casi, questo avviene automaticamente:

Normalmente, i nomi utilizzati nei programmi Python sono internati automaticamente ei dizionari utilizzati per contenere gli attributi di modulo, classe o istanza hanno chiavi internate.

sys.intern è esposto in modo che si può utilizzare per le prestazioni:

internato stringhe è utile per guadagnare un po 'di prestazioni in consultazione dei dizionari - se le chiavi in ​​un dizionario sono internati (dopo profilatura!) e la chiave di ricerca è internata, i confronti chiave (dopo l'hashing) possono essere eseguiti da un confronto di puntatore invece di un confronto di stringhe.

Nota che intern è un incorporato in Python 2.

+1

+1 per il collegamento del codice sorgente pertinente. Interessante, anche se i dettagli sulle stringhe interne per l'ottimizzazione delle ricerche nel dizionario non sono rilevanti per me in questo momento. – EriF89