Como modificar as entradas da lista durante o loop for?

178

Agora eu sei que não é seguro modificar a lista durante um loop iterativo. No entanto, suponha que eu tenha uma lista de seqüências de caracteres e que eu queira desmontá-las. A substituição de valores mutáveis ​​conta como modificação?

Alex
fonte
20
Uma string não produz um valor mutável.
user470379
4
@ user470379: o fato de os elementos da lista serem mutáveis ​​não é relevante para saber se é seguro ou não modificar a lista em que estão enquanto circulam por ela.
martineau

Respostas:

144

É considerado uma forma ruim. Em vez disso, use uma compreensão de lista, com atribuição de fatia, se precisar manter as referências existentes na lista.

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)
Ignacio Vazquez-Abrams
fonte
10
A atribuição da fatia é inteligente e evita a modificação do original durante o loop, mas requer a criação de uma lista temporária do tamanho do original.
22680 martineau
11
por que atribuímos b = a?
Vigrond
9
@ Vigrond: Então, quando a print binstrução é executada, você pode dizer se afoi modificado no local e não substituído. Outra possibilidade seria print b is averificar se os dois ainda se referem ao mesmo objeto.
martineau
12
por que um [:] = e não apenas um =?
kdubs
10
@kdubs: "... com atribuição de fatia, se você precisar manter as referências existentes na lista."
Ignacio Vazquez-Abrams
162

Como o loop abaixo modifica apenas os elementos já vistos, seria considerado aceitável:

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

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

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

O que é diferente de:

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

na medida em que não requer a criação de uma lista temporária e uma atribuição para substituir a original, embora exija mais operações de indexação.

Cuidado: Embora você possa modificar as entradas dessa maneira, não é possível alterar o número de itens listsem arriscar a chance de encontrar problemas.

Aqui está um exemplo do que quero dizer - excluir uma entrada atrapalha a indexação a partir desse ponto:

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!

(O resultado está errado porque não excluiu todos os itens que deveria ter.)

Atualizar

Como esta é uma resposta bastante popular, veja como excluir efetivamente as entradas "no local" (mesmo que essa não seja exatamente a pergunta):

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

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

print(b)  # -> ['a']  # CORRECT
Martineau
fonte
3
Por que o Python faz apenas uma cópia do elemento individual na sintaxe for i in a? Isso é muito contra-intuitivo, aparentemente diferente de outros idiomas e resultou em erros no meu código que eu tive que depurar por um longo período de tempo. O Tutorial do Python nem sequer menciona isso. Embora deva haver alguma razão para isso?
Xji
1
@JIXiang: Não faz cópias. Ele apenas atribui o nome da variável do loop a elementos ou valores sucessivos da coisa que está sendo iterada.
27517 martineau
1
Eca, por que usar dois nomes ( a[i]e s) para o mesmo objeto na mesma linha quando você não precisa? Eu prefiro fazer a[i] = a[i].strip().
Navin
3
@ Navin: Porque a[i] = s.strip()apenas uma operação de indexação.
martineau
1
O @martineau enumerate(b)faz uma operação de indexação em cada iteração e você faz outra a[i] =. AFAIK é impossível implementar esse loop em Python com apenas 1 operação de indexação por iteração de loop :(
Navin
18

Mais uma variante de loop, para mim parece mais limpa do que uma com 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
Eugene Shatsky
fonte
19
Muitos consideram o uso de algo como range(len(list))no Python um cheiro de código.
martineau
2
@ Reishin: Como enumerateé um gerador, não está criando uma lista. De tuplas, ele as cria uma de cada vez, conforme itera na lista. A única maneira de dizer qual é mais lento seria timeit.
Martineau 5/05
3
@martineau código poderia não ser bem bonita, mas de acordo com timeit enumerateé mais lento
Reishin
2
@ Reishin: Seu código de benchmarking não é completamente válido porque não leva em conta a necessidade de recuperar o valor na lista no índice fornecido - o que também não é mostrado nesta resposta.
18715 martineau
4
@ Reishin: Sua comparação é inválida exatamente por esse motivo. Está medindo a sobrecarga de loop isoladamente. Para ser conclusivo, o tempo que leva a execução de todo o loop deve ser medido devido à possibilidade de que quaisquer diferenças de custos indiretos sejam atenuadas pelos benefícios fornecidos ao código dentro do loop de loop de uma certa maneira - caso contrário, você não está comparando maçãs com maçãs
martineau
11

Modificar cada elemento durante a iteração de uma lista é bom, desde que você não altere a adição / remoção de elementos na lista.

Você pode usar a compreensão da lista:

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

ou apenas faça o C-styleloop for:

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

Não, você não alteraria o "conteúdo" da lista, se pudesse modificar as strings dessa maneira. Mas em Python eles não são mutáveis. Qualquer operação de string retorna uma nova string.

Se você tivesse uma lista de objetos que sabia que eram mutáveis, poderá fazer isso desde que não altere o conteúdo real da lista.

Assim, você precisará fazer um mapa de algum tipo. Se você usar uma expressão geradora, a [operação] será feita conforme você itera e você economiza memória.

Skurmedel
fonte
4

Você pode fazer algo assim:

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

Isso se chama compreensão de lista, para facilitar o loop dentro de uma lista.

Nenoj
fonte
3

A resposta de Jemshit Iskenderov e Ignacio Vazquez-Abrams é realmente boa. Pode ser ilustrado com este exemplo: imagine que

a) Uma lista com dois vetores é fornecida a você;

b) você gostaria de percorrer a lista e reverter a ordem de cada uma das matrizes

Digamos que você tenha

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

Você vai ter

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

Por outro lado, se você fizer

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

O resultado é

[array([4, 3, 2, 1]), array([6, 4, 3])]
Rafael Monteiro
fonte
1

Não está claro na sua pergunta quais são os critérios para decidir quais seqüências de caracteres remover, mas se você tiver ou puder fazer uma lista das cadeias que deseja remover, faça o seguinte:

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)

que altera minhas_strings para ['a', 'c', 'e']

Jorge
fonte
0

Em resumo, fazer modificações na lista enquanto itera a mesma lista.

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

exemplo:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
siva balan
fonte