Per il primo esempio è piuttosto chiaro che è caso di ottimizzazione di riferimento unico (ci sono due riferimenti realtà: uno dall'oggetto stesso e uno LOAD_FAST
; unicode_concatenate
cercherà di ridurlo a 1 prima di passare il controllo al PyUnicode_Append
) fatto da CPython utilizzando questa funzione unicode_modifiable
:
static int
unicode_modifiable(PyObject *unicode)
{
assert(_PyUnicode_CHECK(unicode));
if (Py_REFCNT(unicode) != 1)
return 0;
if (_PyUnicode_HASH(unicode) != -1)
return 0;
if (PyUnicode_CHECK_INTERNED(unicode))
return 0;
if (!PyUnicode_CheckExact(unicode))
return 0;
#ifdef Py_DEBUG
/* singleton refcount is greater than 1 */
assert(!unicode_is_singleton(unicode));
#endif
return 1;
}
Ma nel secondo caso come dati di istanza viene memorizzato in un pitone dict
piuttosto che una variabile semplice, così le cose si po 'diverso.
a.accum_ += 'foo'
richiede effettivamente precaricare il valore di a.accum_
e riporlo nello stack. Quindi, ora la stringa ha almeno tre riferimenti: uno dal dizionario di istanza, uno da DUP_TOP
e uno da PyObject_GetAttr
utilizzato da LOAD_ATTR
. Quindi, Python non può ottimizzare questo caso poiché la modifica di uno di essi influirà anche su altri riferimenti.
>>> class A:
pass
...
>>> a = A()
>>> def func():
a.str = 'spam'
print a.str
return '_from_func'
...
>>> a.str = 'foo'
>>> a.str += func()
spam
che ci si aspetta l'uscita di qui per essere 'spam_from_func'
, ma sta per essere diverso, perché il valore originale di a.str
è stato conservato da Python prima func()
è stato chiamato.
>>> a.str
'foo_from_func'
Codice Byte:
>>> import dis
>>> def func_class():
a = Foo()
a.accum = ''
a.accum += 'zzzzz\n'
...
>>> dis.dis(func_class)
2 0 LOAD_GLOBAL 0 (Foo)
3 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
6 STORE_FAST 0 (a)
3 9 LOAD_CONST 1 ('')
12 LOAD_FAST 0 (a)
15 STORE_ATTR 1 (accum)
4 18 LOAD_FAST 0 (a)
21 DUP_TOP
22 LOAD_ATTR 1 (accum)
25 LOAD_CONST 2 ('zzzzz\n')
28 INPLACE_ADD
29 ROT_TWO
30 STORE_ATTR 1 (accum)
33 LOAD_CONST 0 (None)
36 RETURN_VALUE
Si noti che questa ottimizzazione è stato fatto in around 2004 (CPython 2.4) per impedire agli utenti la lentezza di a += b
a += b
o , quindi è principalmente pensato per variabili semplici e funziona solo se l'istruzione successiva è STORE_FAST
(variabile locale), STORE_DEREF
(chiusure) e STORE_NAME
. Non è una soluzione generale, the best way to do this in Python is to create a list and join its items using str.join
.
CPython dettaglio di implementazione: Se s
e t
sono entrambe le stringhe, alcune implementazioni di Python, come CPython solito può eseguire un ottimizzazione sul posto per le assegnazioni della forma s = s + t
o s += t
. Quando applicabile a , questa ottimizzazione rende probabile un tempo di esecuzione quadratico molto inferiore a . Questa ottimizzazione è dipendente dalla versione e dall'implementazione . Per codice sensibile alle prestazioni, è preferibile utilizzare il metodo str.join()
che assicura la costante concatenazione lineare delle prestazioni tra versioni e implementazioni.
È sicuramente perché nel primo approccio è il primo approccio la stringa avrà un solo riferimento durante la sua durata e CPython può ottimizzare questo caso, quindi O (N) tempo. Nel secondo caso il conteggio dei riferimenti è sicuramente aumentato da 1 (ma esattamente dove?), Quindi il tempo quadratico. –
@AshwiniChaudhary: il conteggio dei riferimenti è lo stesso in entrambi i casi, ma come indica la risposta, c'è un ottimizzazione specifica rispetto al primo caso, ma non rispetto al secondo, ed è per questo che il secondo caso richiede un tempo quadratico. – doublep
@doublep Non sono sicuro di come stai cercando di contare i riferimenti, ma controlla il refcount prima di '+ =' e ** durante ** '+ ='. Dovrebbe aumentare di almeno 1. –