Python, циклы и замыкания

Я достаточно опытный программист на C/C++ (и в некоторой степени на Java). Я изучаю python, но я сбит с толку некоторыми странными (для моего фона) поведением языка.

Я изучаю вложенные функции и замыкания (читая «Изучение Python», это кажется мне действительно хорошим источником).

Я понимаю, что если я вложу определение в цикл for, когда я вызову созданную функцию, она ищет последнее значение захваченной переменной цикла (поскольку она захватывает по ссылке, как сказал бы программист C++)

funcs = []
for i in range(4):
    def f():
        print(i)
    funcs.append(f)

и запустив программу, результат

>>> for f in funcs:
      f()


3
3
3
3

Теперь, я обдумывал это, когда наткнулся на это (как мне кажется) несоответствие: если я делаю

for i in range(4):
  funcs[i]()


0
1
2
3

больше сбивает с толку, если я

>>> i = 2
>>> funcs[i]()

2

и теперь все функции в списке возвращают 2:

for f in funcs:
  f()


2
2
2
2

должен быть какой-то вопрос, связанный с объемом, который я не могу понять


person Luca    schedule 21.01.2019    source источник
comment
Ваш первый опубликованный код не работает так, как вы ожидаете, поскольку отступ неправильный.   -  person mrCarnivore    schedule 21.01.2019
comment
извините, это ошибка копирования-вставки, мой код правильный, редактирую его сейчас   -  person Luca    schedule 21.01.2019
comment
Я думаю, что вы перепутали отступ на funcs.append(f). Я мог бы быть не в духе здесь, но print(i) напечатает последнее сгенерированное i. В вашем случае это for i in range(4):, а не когда вы определили функцию. Так что не странно, что вы получаете этот вывод. Таким образом, i не замораживается при создании функции, а является живым значением. Вы печатаете последнее известное/установленное значение i.   -  person Torxed    schedule 21.01.2019
comment
Если я запущу программу так, как вы ее отредактировали, сейчас на выходе будет только одна «3»…   -  person mrCarnivore    schedule 21.01.2019
comment
Я также не могу воспроизвести второй пример. Пожалуйста, исправьте отступы и напишите полный код, чтобы мы могли вам помочь!   -  person mrCarnivore    schedule 21.01.2019
comment
@mrCarnivore, что не так с отступом? это всего лишь один блок операторов с отступом на один уровень   -  person Luca    schedule 21.01.2019
comment
@Torxed вызов append() находится внутри цикла for, так как мне нужно создать список объектов функций   -  person Luca    schedule 21.01.2019
comment
@ Лука Да, конечно. Я отредактировал ваш вопрос до того, как вы отредактировали его три раза подряд, потому что я предполагал, что это так. Тем не менее. print(i) не будет печатать i из def f()-цикла, а for i in range(4): funcs[i]()-цикла. Смотрите мой комментарий выше.   -  person Torxed    schedule 21.01.2019
comment
Хотя в приведенных ниже ответах уже сказано все, вы можете ознакомиться с пояснениями здесь и здесь.   -  person mportes    schedule 21.01.2019


Ответы (3)


Во-первых, это создает список из четырех функций.

funcs = []
for i in range(4):
  def f():
    print(i)
  funcs.append(f)

Каждая из этих функций ищет значение i, а затем печатает его.

Это перебирает список функций и вызывает каждую из них:

>>> for f in funcs:
      f()

Как указано выше, эти функции ищут i, что сейчас равно 3 из-за цикла for i in range(4), который завершился ранее, поэтому вы получаете четыре распечатки 3.

Теперь вы снова зацикливаетесь, используя i в качестве переменной цикла:

for i in range(4):
  funcs[i]()


0
1
2
3

В первый раз в цикле i равно 0, поэтому, когда функция ищет i, она получает 0, а затем печатает его. Затем он меняется на 1, затем на 2, затем на 3.

Следующий код просто изменяет i еще одним способом и вызывает функцию:

>>> i = 2
>>> funcs[i]()

2

Вы могли бы вызвать любую из этих функций, и они все равно напечатали бы 2, потому что сейчас это значение i. Вы просто заблудились, потому что вы зациклились на range(4), чтобы создать эти функции, затем вы зациклились на range(4), чтобы проиндексировать список функций, и вы продолжаете повторно использовать i, а затем переназначаете i, а также используете его для индексации списка.

Если вы хотите, чтобы напечатанное значение i каждой функции было зафиксировано на том уровне, каким оно было при определении функции, самый простой способ сделать это — использовать аргумент по умолчанию, поскольку они оцениваются при определении функции, а не при ее вызове:

funcs = []
for i in range(4):
  def f(num=i):
    print(num)
  funcs.append(f)
person TigerhawkT3    schedule 21.01.2019

Ваши функции

def f():
    print(i)

напечатать текущее значение i.

Если вы пишете

for i in range(4):
    funcs[i]()

затем i устанавливается в 0,1,2,3 по мере прохождения цикла. Вот что означает for i in range(4).

Если вы пишете

for f in funcs:
    f()

затем i продолжается с тем значением, которое у него уже было.

person khelwood    schedule 21.01.2019

Здесь нет нестыковки. Значение i в f() зависит от значения i из родительской области. После того, как вы запустите первый for i in range(4), i будет иметь значение последнего элемента в range, равное 3, и, таким образом, все последующие вызовы f() будут печатать 3

Если вы запустите

for i in range(4):
  funcs[i]()

вы переопределяете значение i на каждом шаге итерации, и поэтому вы получаете 0,1,2,3 в качестве значений, напечатанных f. Делает

for x in range(4):
   funcs[x]()

не повлияет на значение i, поэтому вы получите 3 в качестве значения i во всех вызовах функций

person Irina Velikopolskaya    schedule 21.01.2019