Как изменить записи списка во время цикла for?

178

Теперь я знаю, что изменять список во время итеративного цикла небезопасно. Однако предположим, что у меня есть список строк, и я хочу удалить сами строки. Считается ли замена изменяемых значений модификацией?

Alex
источник
20
Строка не изменяемое значение make.
user470379
4
@ user470379: Независимо от того, являются ли элементы списка изменчивыми, не имеет значения, безопасно или нет изменять список, в котором они находятся, во время его цикла.
Мартино

Ответы:

144

Это считается плохой формой. Вместо этого используйте понимание списка с назначением срезов, если вам нужно сохранить существующие ссылки на список.

a = [1, 3, 5]
b = a
a[:] = [x + 2 for x in a]
print(b)
Игнасио Васкес-Абрамс
источник
10
Назначение среза является умным и позволяет избежать изменения оригинала во время цикла, но требует создания временного списка длины оригинала.
Мартино
11
почему мы назначаем b = a?
Вигронд
9
@Vigrond: Таким образом, когда print bоператор выполняется, вы можете сказать, aбыл ли он изменен на месте, а не заменен. Другой возможностью было бы print b is aувидеть, ссылаются ли они оба на один и тот же объект.
Мартино
12
почему [:] = а не просто =?
kdubs
10
@kdubs: «... с назначением фрагмента, если вам нужно сохранить существующие ссылки на список».
Игнасио Васкес-Абрамс
163

Поскольку приведенный ниже цикл изменяет только элементы, которые уже видны, он будет считаться приемлемым:

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

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

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

Который отличается от:

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

в том смысле, что он не требует создания временного списка и его назначения для замены оригинала, хотя для этого требуется больше операций индексирования.

Предостережение. Хотя вы можете изменить записи таким способом, вы не можете изменить количество элементов в нем, listне рискуя столкнуться с проблемами.

Вот пример того, что я имею в виду - удаление записи портит индексацию с этого момента:

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!

(Результат неверный, потому что он не удалил все элементы, которые он должен иметь.)

Обновить

Поскольку это довольно популярный ответ, вот как эффективно удалять записи «на месте» (хотя это не совсем вопрос):

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

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

print(b)  # -> ['a']  # CORRECT
Мартино
источник
3
Почему Python делает копию только отдельного элемента в синтаксисе for i in a? Это очень нелогично, похоже, отличается от других языков и привело к ошибкам в моем коде, которые мне приходилось отлаживать в течение длительного периода времени. Python Tutorial даже не упоминает об этом. Хотя должна быть какая-то причина?
xji
1
@JIXiang: он не делает копии. Он просто присваивает имя переменной цикла последовательным элементам или значение объекта, который повторяется.
Мартино
1
Eww, зачем использовать два имени ( a[i]и s) для одного и того же объекта в одной строке, когда вам не нужно? Я бы предпочел сделать a[i] = a[i].strip().
Навин
3
@Navin: Потому что a[i] = s.strip()только одна операция индексации.
Мартино
1
@martineau enumerate(b)выполняет операцию индексации на каждой итерации, а вы выполняете еще одну a[i] =. AFAIK Невозможно реализовать этот цикл в Python, используя только одну операцию индексации на одну итерацию цикла :(
Navin
18

Еще один вариант цикла, мне кажется, чище, чем 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
Многие считают использование чего-то похожего range(len(list))на Python запахом кода.
Мартино
2
@Reishin: Так enumerateкак это генератор, он не создает список кортежей, он создает их по одному за раз, просматривая список. Единственный способ определить, что медленнее, - это timeit.
Мартино
3
Код @martineau может быть не очень красивым, но, по словам timeit enumerate, медленнее
Рейшин
2
@Reishin: Ваш контрольный код не полностью действителен, поскольку он не учитывает необходимость извлечения значения из списка по заданному индексу - что также не показано в этом ответе.
Мартино
4
@Reishin: Ваше сравнение неверно именно по этой причине. Он измеряет издержки зацикливания в изоляции. Чтобы быть окончательным, необходимо измерить время, необходимое для выполнения всего цикла, поскольку существует вероятность того, что любые различия в служебной нагрузке могут быть смягчены преимуществами, предоставляемыми коду внутри цикла определенного цикла, иначе вы не будете сравнивать яблоки с яблоки.
Мартино
11

Модификация каждого элемента во время итерации списка - это хорошо, если вы не изменяете добавление / удаление элементов в список.

Вы можете использовать понимание списка:

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

или просто сделайте C-styleцикл for:

for index, item in enumerate(l):
    l[index] = item.strip()
cizixs
источник
4

Нет, вы не изменили бы «содержимое» списка, если бы вы могли изменять строки таким образом. Но в Python они не изменчивы. Любая строковая операция возвращает новую строку.

Если бы у вас был список объектов, которые, как вы знали, были изменяемыми, вы могли бы делать это до тех пор, пока вы не измените фактическое содержимое списка.

Таким образом, вам нужно будет сделать карту какого-то рода. Если вы используете выражение генератора, оно [операция] будет выполнено во время итерации, и вы сэкономите память.

Skurmedel
источник
4

Вы можете сделать что-то вроде этого:

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

Это называется пониманием списка, чтобы упростить цикл внутри списка.

Nenoj
источник
3

Ответ Джемшита Искендерова и Игнасио Васкеса-Абрамса действительно хорош. Это может быть дополнительно проиллюстрировано на этом примере: представьте, что

а) Вам дан список с двумя векторами;

б) вы хотели бы пройти список и изменить порядок каждого из массивов

Допустим, у вас есть

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

Ты получишь

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

С другой стороны, если вы делаете

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

Результат

[array([4, 3, 2, 1]), array([6, 4, 3])]
Рафаэль Монтейро
источник
1

Из вашего вопроса неясно, каковы критерии принятия решения о том, какие строки удалять, но если у вас есть или можно составить список строк, которые вы хотите удалить, вы можете сделать следующее:

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)

который меняет my_strings на ['a', 'c', 'e']

Jorge
источник
0

Короче говоря, чтобы внести изменения в список, повторяя тот же список.

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

пример:

list[:] = [list.remove(each_element) for each_element in list if each_element in ["data1", "data2"]]
Шива Балан
источник