Теперь я знаю, что изменять список во время итеративного цикла небезопасно. Однако предположим, что у меня есть список строк, и я хочу удалить сами строки. Считается ли замена изменяемых значений модификацией?
@ user470379: Независимо от того, являются ли элементы списка изменчивыми, не имеет значения, безопасно или нет изменять список, в котором они находятся, во время его цикла.
Мартино
Ответы:
144
Это считается плохой формой. Вместо этого используйте понимание списка с назначением срезов, если вам нужно сохранить существующие ссылки на список.
Назначение среза является умным и позволяет избежать изменения оригинала во время цикла, но требует создания временного списка длины оригинала.
Мартино
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
Почему 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
Многие считают использование чего-то похожего range(len(list))на Python запахом кода.
Мартино
2
@Reishin: Так enumerateкак это генератор, он не создает список кортежей, он создает их по одному за раз, просматривая список. Единственный способ определить, что медленнее, - это timeit.
Мартино
3
Код @martineau может быть не очень красивым, но, по словам timeitenumerate, медленнее
Рейшин
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()
Нет, вы не изменили бы «содержимое» списка, если бы вы могли изменять строки таким образом. Но в Python они не изменчивы. Любая строковая операция возвращает новую строку.
Если бы у вас был список объектов, которые, как вы знали, были изменяемыми, вы могли бы делать это до тех пор, пока вы не измените фактическое содержимое списка.
Таким образом, вам нужно будет сделать карту какого-то рода. Если вы используете выражение генератора, оно [операция] будет выполнено во время итерации, и вы сэкономите память.
Ответ Джемшита Искендерова и Игнасио Васкеса-Абрамса действительно хорош. Это может быть дополнительно проиллюстрировано на этом примере: представьте, что
а) Вам дан список с двумя векторами;
б) вы хотели бы пройти список и изменить порядок каждого из массивов
Допустим, у вас есть
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 stringprint([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 stringprint([v,b])
Из вашего вопроса неясно, каковы критерии принятия решения о том, какие строки удалять, но если у вас есть или можно составить список строк, которые вы хотите удалить, вы можете сделать следующее:
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)
Ответы:
Это считается плохой формой. Вместо этого используйте понимание списка с назначением срезов, если вам нужно сохранить существующие ссылки на список.
источник
print b
оператор выполняется, вы можете сказать,a
был ли он изменен на месте, а не заменен. Другой возможностью было быprint b is a
увидеть, ссылаются ли они оба на один и тот же объект.Поскольку приведенный ниже цикл изменяет только элементы, которые уже видны, он будет считаться приемлемым:
Который отличается от:
в том смысле, что он не требует создания временного списка и его назначения для замены оригинала, хотя для этого требуется больше операций индексирования.
Предостережение. Хотя вы можете изменить записи таким способом, вы не можете изменить количество элементов в нем,
list
не рискуя столкнуться с проблемами.Вот пример того, что я имею в виду - удаление записи портит индексацию с этого момента:
(Результат неверный, потому что он не удалил все элементы, которые он должен иметь.)
Обновить
Поскольку это довольно популярный ответ, вот как эффективно удалять записи «на месте» (хотя это не совсем вопрос):
источник
for i in a
? Это очень нелогично, похоже, отличается от других языков и привело к ошибкам в моем коде, которые мне приходилось отлаживать в течение длительного периода времени. Python Tutorial даже не упоминает об этом. Хотя должна быть какая-то причина?a[i]
иs
) для одного и того же объекта в одной строке, когда вам не нужно? Я бы предпочел сделатьa[i] = a[i].strip()
.a[i] = s.strip()
только одна операция индексации.enumerate(b)
выполняет операцию индексации на каждой итерации, а вы выполняете еще однуa[i] =
. AFAIK Невозможно реализовать этот цикл в Python, используя только одну операцию индексации на одну итерацию цикла :(Еще один вариант цикла, мне кажется, чище, чем enumerate ():
источник
range(len(list))
на Python запахом кода.enumerate
как это генератор, он не создает список кортежей, он создает их по одному за раз, просматривая список. Единственный способ определить, что медленнее, - этоtimeit
.timeit
enumerate
, медленнееМодификация каждого элемента во время итерации списка - это хорошо, если вы не изменяете добавление / удаление элементов в список.
Вы можете использовать понимание списка:
или просто сделайте
C-style
цикл for:источник
Нет, вы не изменили бы «содержимое» списка, если бы вы могли изменять строки таким образом. Но в Python они не изменчивы. Любая строковая операция возвращает новую строку.
Если бы у вас был список объектов, которые, как вы знали, были изменяемыми, вы могли бы делать это до тех пор, пока вы не измените фактическое содержимое списка.
Таким образом, вам нужно будет сделать карту какого-то рода. Если вы используете выражение генератора, оно [операция] будет выполнено во время итерации, и вы сэкономите память.
источник
Вы можете сделать что-то вроде этого:
Это называется пониманием списка, чтобы упростить цикл внутри списка.
источник
Ответ Джемшита Искендерова и Игнасио Васкеса-Абрамса действительно хорош. Это может быть дополнительно проиллюстрировано на этом примере: представьте, что
а) Вам дан список с двумя векторами;
б) вы хотели бы пройти список и изменить порядок каждого из массивов
Допустим, у вас есть
Ты получишь
С другой стороны, если вы делаете
Результат
источник
Из вашего вопроса неясно, каковы критерии принятия решения о том, какие строки удалять, но если у вас есть или можно составить список строк, которые вы хотите удалить, вы можете сделать следующее:
который меняет my_strings на ['a', 'c', 'e']
источник
Короче говоря, чтобы внести изменения в список, повторяя тот же список.
пример:
источник