2012-03-16 4 views
4

У меня странная проблема. Кто-нибудь видит что-то не так с моим кодом?Python for loop пропускает каждый цикл?

for x in questions: 
    forms.append((SectionForm(request.POST, prefix=str(x.id)),x)) 
    print "Appended " + str(x) 
for (form, question) in forms: 
    print "Testing " + str(question) 
    if form.is_valid(): 
     forms.remove((form,question)) 
     print "Deleted " + str(question) 
     a = form.save(commit=False) 
     a.audit = audit 
     a.save()     
    else: 
     flag_error = True 

Результаты в:

Appended Question 50 
Appended Question 51 
Appended Question 52 
Testing Question 50 
Deleted Question 50 
Testing Question 52 
Deleted Question 52 

кажется, чтобы пропустить вопрос 51. добавляемой к списку, но цикл пропускает его. Есть идеи?

ответ

11

Вы изменяете содержимое объекта forms что вы Перебор, когда вы говорите:

forms.remove((form,question)) 

Согласно Python documentation of the for statement, это не безопасно (выделено мной):

Оператор for в Python немного отличается от того, к чему вы можете использовать в C или Pascal. Вместо того, чтобы всегда выполнять арифметическую прогрессию чисел (например, в Pascal) или давать пользователю возможность определять как шаг итерации, так и условие остановки (как C), оператор Python выполняет итерации по элементам любой последовательности (список или строка), в порядке их появления в последовательности.

Неверно модифицировать последовательность, повторяющуюся в цикле (это может произойти только для изменяемых типов последовательностей, таких как списки). Если вам нужно изменить список, который вы повторяете (например, чтобы дублировать выбранные элементы), вы должны перебирать копию. Срез обозначение делает это особенно удобно:

for x in a[:]: # make a slice copy of the entire list 
... if len(x) > 6: a.insert(0, x) 

Смотрите также этот пункт из Python Language Reference, который объясняет, что именно происходит:

Существует тонкость, когда последовательность быть модифицирована с помощью (это может произойти только для изменяемых последовательностей, т. е. списков). Внутренний счетчик используется для отслеживания того, какой элемент используется далее, и это увеличивается на каждой итерации. Когда этот счетчик достигнет длины последовательности, цикл завершается. Это означает, что если пакет удаляет текущий (или предыдущий) элемент из последовательности, следующий элемент будет пропущен (поскольку он получает индекс текущего элемента, который уже был обработан). Аналогично, если пакет вставляет элемент в последовательность перед текущим элементом, текущий элемент будет обрабатываться снова в следующий раз через цикл.

Существует множество решений. Вы можете следовать их советам и создать копию. Другая возможность - создать новый список в результате второго цикла for, а не напрямую изменять forms. Выбор зависит от вас ...

+0

Где написано, что это не разрешено? – Marcin

+0

Итак, в этом случае я бы предложил пометить вопросы для удаления (или сохранить список индексов для вопросов, которые нужно удалить) и фактически удалить их после цикла. – egor83

+0

Хорошая точка, это не безопасно, но разрешено. –

3

Вы удаляете объекты из forms, итерации по нему. Это должно привести к поведению, которое вы видите (http://docs.python.org/reference/compound_stmts.html#the-for-statement).

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

2

Использование метода удаления в формах (который я предполагаю, это список) изменяет размер списка.Так думайте об этом таким образом

[ 50, 51, 52 ] 

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

[51, 52] 

Но теперь вы просите второй пункт, так что вы получите 52.