Як змінити записи списку під час циклу?


178

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


20
Рядок не має значення, що змінюється.
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
чому a [:] =, а не просто a =?
kdubs

10
@kdubs: "... з призначенням фрагментів, якщо вам потрібно зберегти існуючі посилання на список."
Ігнасіо Васкес-Абрамс

162

Оскільки цикл внизу лише змінює елементи, які вже були помічені, це вважатиметься прийнятним:

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 навіть не згадує про це. Хоча для цього повинні бути якісь причини?
xji

1
@JIXiang: копії не створюються. Він просто присвоює ім'я змінної циклу послідовним елементам або значенням речі, яка повторюється.
мартіно

1
Eww, навіщо використовувати два імена ( a[i]і s) для одного і того ж об'єкта в одному рядку, коли не потрібно? Я б набагато краще зробив a[i] = a[i].strip().
Навін

3
@Navin: Тому що виконується a[i] = s.strip()лише одна операція індексації.
martineau

1
@martineau enumerate(b)робить операцію індексації для кожної ітерації, а ви робите ще одну a[i] =. AFAIK неможливо реалізувати цю петлю в Python лише з 1 операцією індексації за цикл ітерації :(
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це генератор, він не створює list.of кортежів, він створює їх по одному, як він повторює список. Єдиним способом визначити, що повільніше, було б timeit.
мартіно

3
Код @martineau міг би бути не дуже гарним, але відповідно до timeit enumerateцього повільніше
Reishin

2
@Reishin: Ваш код бенчмаркінгу не є повністю дійсним, оскільки він не враховує необхідність отримання значення у списку за вказаним індексом, що також не відображено у цій відповіді.
мартіно

4
@Reishin: Ваше порівняння недійсне саме з цієї причини. Це вимірювання петлі над головою ізольовано. Щоб бути остаточним, час, на який потрібно виконати весь цикл, повинен вимірюватися через можливість того, що будь-які накладні відмінності можуть бути пом'якшені певними способами, що надаються коду всередині циклу циклічного циклу - інакше ви не порівнюєте яблука з яблука.
мартіно

11

Змінення кожного елемента під час ітерації списку є нормальним, доки ви не зміните додавання / видалення елементів до списку.

Ви можете використовувати розуміння списку:

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

або просто зробіть C-styleцикл для:

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

4

Ні, ви б не змінювали "вміст" списку, якби ви могли таким чином змінити рядки. Але в Python вони не змінюються. Будь-яка операція рядка повертає новий рядок.

Якщо у вас був список об'єктів, які ви знали, що вони можуть змінюватися, ви можете це робити до тих пір, поки не зміните фактичний вміст списку.

Таким чином, вам потрібно буде зробити якусь карту. Якщо ви використовуєте генераторний вираз, це [операція] буде виконано під час ітерації, і ви збережете пам'ять.


4

Ви можете зробити щось подібне:

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

Це називається розумінням списку, щоб вам було простіше зациклюватися на ньому.


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)

який міняє мої строки на ['a', 'c', 'e']


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"]]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.