tuple - unire due liste python



Tupla o lista quando si usa 'in' in una clausola 'if'? (1)

Quale approccio è migliore? Usando una tupla, come:

if number in (1, 2):

o una lista, come:

if number in [1, 2]:

Quale è consigliato per tali usi e perché (sia logico che prestazionale)?


L'interprete CPython sostituisce il secondo modulo con il primo .

Questo perché caricare la tupla da una costante è un'operazione, ma la lista dovrebbe essere 3 operazioni; carica i due contenuti interi e crea un nuovo oggetto elenco.

Poiché stai utilizzando una lista letterale che non è altrimenti raggiungibile, è sostituita da una tupla:

>>> import dis
>>> dis.dis(compile('number in [1, 2]', '<stdin>', 'eval'))
  1           0 LOAD_NAME                0 (number)
              3 LOAD_CONST               2 ((1, 2))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE        

Qui il secondo bytecode carica una tupla (1, 2) come costante, in un unico passaggio. Confronta questo con la creazione di un oggetto elenco non utilizzato in un test di appartenenza:

>>> dis.dis(compile('[1, 2]', '<stdin>', 'eval'))
  1           0 LOAD_CONST               0 (1)
              3 LOAD_CONST               1 (2)
              6 BUILD_LIST               2
              9 RETURN_VALUE        

Qui sono necessari N + 1 passaggi per un oggetto elenco di lunghezza N.

Questa sostituzione è un'ottimizzazione dello spioncino specifica di CPython; vedi il sorgente Python/peephole.c . Per le altre implementazioni di Python, quindi, si vuole invece attaccare con oggetti immutabili.

Detto questo, l'opzione migliore quando si utilizza Python 3.2 e versioni successive è utilizzare un set letterale :

if number in {1, 2}:

poiché l'ottimizzatore dello spioncino sostituirà quello con un oggetto frozenset() e i test di appartenenza contro i set sono operazioni costanti O (1):

>>> dis.dis(compile('number in {1, 2}', '<stdin>', 'eval'))
  1           0 LOAD_NAME                0 (number)
              3 LOAD_CONST               2 (frozenset({1, 2}))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE

Questa ottimizzazione è stata aggiunta in Python 3.2 ma non è stata trasferita a Python 2.

Pertanto, l'ottimizzatore Python 2 non riconosce questa opzione e il costo di creazione di un set o frozenset dai contenuti è quasi sicuramente più costoso rispetto all'utilizzo di una tupla per il test.

I test di appartenenza impostati sono O (1) e veloci; testare contro una tupla è O (n) nel peggiore dei casi. Anche se testare di nuovo un set deve calcolare l'hash (costo costante più elevato, memorizzato nella cache per tup immutabili), il costo per il test rispetto a una tupla diversa dal primo elemento sarà sempre maggiore. Quindi, in media, i set sono facilmente più veloci:

>>> import timeit
>>> timeit.timeit('1 in (1, 3, 5)', number=10**7)  # best-case for tuples
0.21154764899984002
>>> timeit.timeit('8 in (1, 3, 5)', number=10**7)  # worst-case for tuples
0.5670104179880582
>>> timeit.timeit('1 in {1, 3, 5}', number=10**7)  # average-case for sets
0.2663505630043801
>>> timeit.timeit('8 in {1, 3, 5}', number=10**7)  # worst-case for sets
0.25939063701662235




python-internals