Come modificare le voci dell'elenco durante il ciclo for?


178

Ora so che non è sicuro modificare l'elenco durante un ciclo iterativo. Tuttavia, supponiamo di avere un elenco di stringhe e di voler rimuovere le stringhe stesse. La sostituzione di valori mutabili conta come modifica?


20
Una stringa non crea un valore mutabile.
user470379

4
@ user470379: Il fatto che gli elementi dell'elenco siano modificabili non è rilevante per stabilire se è sicuro o meno modificare l'elenco in cui si trovano durante il ciclo.
martineau,

Risposte:


144

È considerata forma scadente. Utilizzare invece una comprensione dell'elenco, con assegnazione delle sezioni se è necessario conservare i riferimenti esistenti all'elenco.

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)

10
L'assegnazione della sezione è intelligente ed evita di modificare l'originale durante il ciclo, ma richiede la creazione di un elenco temporaneo della lunghezza dell'originale.
martineau,

11
perché assegniamo b = a?
Vigrond

9
@Vigrond: Quindi quando print bviene eseguita l' istruzione, puoi dire se è astata modificata sul posto anziché sostituita. Un'altra possibilità sarebbe stata quella print b is adi vedere se entrambi si riferiscono ancora allo stesso oggetto.
martineau,

12
perché a [:] = e non solo a =?
kdubs,

10
@kdubs: "... con assegnazione delle sezioni se è necessario conservare i riferimenti esistenti all'elenco."
Ignacio Vazquez-Abrams,

163

Poiché il ciclo seguente modifica solo gli elementi già visti, sarebbe considerato accettabile:

a = ['a',' b', 'c ', ' d ']

for i, s in enumerate(a):
    a[i] = s.strip()

print(a) # -> ['a', 'b', 'c', 'd']

Che è diverso da:

a[:] = [s.strip() for s in a]

in quanto non richiede la creazione di un elenco temporaneo e una sua assegnazione per sostituire l'originale, sebbene richieda più operazioni di indicizzazione.

Attenzione: sebbene sia possibile modificare le voci in questo modo, non è possibile modificare il numero di elementi nel listsenza rischiare la possibilità di riscontrare problemi.

Ecco un esempio di ciò che intendo: eliminare una voce incasina l'indicizzazione da quel punto in poi:

b = ['a', ' b', 'c ', ' d ']

for i, s in enumerate(b):
    if s.strip() != b[i]:  # leading or trailing whitespace?
        del b[i]

print(b)  # -> ['a', 'c ']  # WRONG!

(Il risultato è sbagliato perché non ha eliminato tutti gli elementi che dovrebbe avere.)

Aggiornare

Poiché questa è una risposta abbastanza popolare, ecco come eliminare efficacemente le voci "sul posto" (anche se non è esattamente la domanda):

b = ['a',' b', 'c ', ' d ']

b[:] = [entry for entry in b if entry.strip() == entry]

print(b)  # -> ['a']  # CORRECT

3
Perché Python crea solo una copia del singolo elemento nella sintassi for i in a? Questo è molto controintuitivo, apparentemente diverso dalle altre lingue e ha provocato errori nel mio codice che ho dovuto eseguire il debug per un lungo periodo di tempo. Python Tutorial non ne parla nemmeno. Anche se ci deve essere qualche motivo?
xji,

1
@JIXiang: non ne fa una copia. Assegna semplicemente il nome della variabile del ciclo agli elementi successivi o al valore della cosa su cui si esegue l'iterazione.
martineau,

1
Eww, perché usare due nomi ( a[i]e s) per lo stesso oggetto nella stessa riga quando non è necessario? Preferirei di gran lunga fare a[i] = a[i].strip().
Navin,

3
@Navin: perché a[i] = s.strip()esegue una sola operazione di indicizzazione.
martineau,

1
@martineau enumerate(b)esegue un'operazione di indicizzazione su ogni iterazione e ne stai facendo un'altra a[i] =. AFAIK è impossibile implementare questo loop in Python con solo 1 operazione di indicizzazione per iterazione di loop :(
Navin

18

Un altro per la variante loop, mi sembra più pulito di uno con enumerate ():

for idx in range(len(list)):
    list[idx]=... # set a new value
    # some other code which doesn't let you use a list comprehension

19
Molti considerano l'utilizzo di qualcosa come range(len(list))in Python un odore di codice.
martineau,

2
@Reishin: Poiché enumerateè un generatore non crea un elenco di tuple, le crea una alla volta mentre scorre l'elenco. L'unico modo per dire quale è più lento sarebbe timeit.
martineau,

3
Il codice @martineau potrebbe non essere carino, ma secondo timeit enumerateè più lento
Reishin,

2
@Reishin: il tuo codice di benchmarking non è completamente valido perché non tiene conto della necessità di recuperare il valore nell'elenco in un determinato indice - che non è mostrato nemmeno in questa risposta.
martineau,

4
@Reishin: il tuo confronto non è valido proprio per questo motivo. Sta misurando il sovraccarico loop in isolamento. Per essere conclusivi, il tempo necessario per l'esecuzione dell'intero loop deve essere misurato a causa della possibilità che eventuali differenze generali possano essere mitigate dai benefici forniti al codice all'interno del loop di looping in un determinato modo, altrimenti non si confronteranno le mele con mele.
martineau,

11

La modifica di ogni elemento durante l'iterazione di un elenco va bene, a condizione che non si cambi aggiungere / rimuovere elementi all'elenco.

È possibile utilizzare la comprensione dell'elenco:

l = ['a', ' list', 'of ', ' string ']
l = [item.strip() for item in l]

o fai semplicemente il C-styleciclo for:

for index, item in enumerate(l):
    l[index] = item.strip()

4

No, non altereresti il ​​"contenuto" dell'elenco, se potessi mutare le stringhe in quel modo. Ma in Python non sono mutabili. Qualsiasi operazione su stringa restituisce una nuova stringa.

Se avessi un elenco di oggetti che sapevi fossero mutabili, potresti farlo purché non modifichi il contenuto effettivo dell'elenco.

Quindi dovrai fare una mappa di qualche tipo. Se si utilizza un'espressione del generatore, l'operazione [l'operazione] verrà eseguita mentre si esegue l'iterazione e si risparmierà memoria.


4

Puoi fare qualcosa del genere:

a = [1,2,3,4,5]
b = [i**2 for i in a]

Si chiama comprensione di un elenco, per rendere più semplice il ciclo all'interno di un elenco.


3

La risposta data da Jemshit Iskenderov e Ignacio Vazquez-Abrams è davvero buona. Può essere ulteriormente illustrato con questo esempio: immaginalo

a) ti viene fornito un elenco con due vettori;

b) si desidera attraversare l'elenco e invertire l'ordine di ciascuna delle matrici

Diciamo che hai

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
    i = i[::-1]   # this command does not reverse the string

print([v,b])

Otterrete

[array([1, 2, 3, 4]), array([3, 4, 6])]

D'altra parte, se lo fai

v = np.array([1, 2,3,4])
b = np.array([3,4,6])

for i in [v, b]:
   i[:] = i[::-1]   # this command reverses the string

print([v,b])

Il risultato è

[array([4, 3, 2, 1]), array([6, 4, 3])]

1

Dalla tua domanda non è chiaro quali siano i criteri per decidere quali stringhe rimuovere, ma se hai o puoi fare un elenco delle stringhe che desideri rimuovere, puoi fare quanto segue:

my_strings = ['a','b','c','d','e']
undesirable_strings = ['b','d']
for undesirable_string in undesirable_strings:
    for i in range(my_strings.count(undesirable_string)):
        my_strings.remove(undesirable_string)

che cambia my_strings in ['a', 'c', 'e']


0

In breve, per apportare modifiche all'elenco durante l'iterazione dello stesso elenco.

list[:] = ["Modify the list" for each_element in list "Condition Check"]

esempio:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.