Как захватить значение в закрытии Python

#python-3.x #closures

Вопрос:

Как захватить значение (в отличие от ссылки) в закрытии python?

Что я пробовал:

Вот функция, которая составляет список функций без параметров, каждая из которых выдает строку:

 def makeListOfFuncs( strings):
  list = []
  for str in strings:
    def echo():
      return str
    list.append( echo)
  return list
 

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

 for animalFunc in makeListOfFuncs( ['bird', 'fish', 'zebra']):
  print( animalFunc())
 

… даст такой результат:

 bird
fish
zebra
 

Но вместо этого в python мы получаем:

 zebra
zebra
zebra
 

По — видимому, происходит то, что закрытие echo функции захватывает ссылку на str, в отличие от значения во фрейме вызова во время построения закрытия.

Как я могу определить функции Makelistoffunces, чтобы получить «птицу», «рыбу», «зебру»?

Ответ №1:

Закрытие Python не является странным, если вы знаете, как внутреннее закрытие работает в python.

 def makeListOfFuncs( strings):
  list = []
  for str in strings:
    def echo():
      return str
    list.append( echo)
  return list
 

Вы возвращаете список закрытий. переменная «str» является общей для областей, она находится в двух разных областях. Когда python видит это, он создает промежуточный объект. этот объект содержит ссылку на другой объект, который является «str». и для каждого закрытия это одна и та же ячейка. Вы можете это проверить:

 closures_list= makeListOfFuncs( ['bird', 'fish', 'zebra'])
 # Those will return same cell address
 closures_list[0].__closure__
 closures_list[1].__closure__
 closures_list[2].__closure__
 

введите описание изображения здесь

При вызове makeListOfFuncs он запустит цикл for , и в конце цикла промежуточный объект укажет на «зебру». Поэтому , когда вы вызываете каждое закрытие print( animalFunc()) , оно посещает промежуточный объект, который будет ссылаться на «зебру».

Вы должны подумать о том, что происходит, когда python создает функцию и когда он ее оценивает.

 def echo():
  return str
 

Нам нужно каким-то образом зафиксировать значение «str» в создаваемой функции. Потому что если вы подождете, пока функция не будет оценена, то будет слишком поздно. Другим решением было бы определение значения по умолчанию:

 def makeListOfFuncs( strings):
    list = []
    for str in strings:
        def echo(y=str):
            return y
        list.append( echo)
    return list

for animalFunc in makeListOfFuncs( ['bird', 'fish', 'zebra']):
    print( animalFunc())
 

Это решение работает, потому что значения по умолчанию оцениваются во время создания. Поэтому при echo создании будет использоваться значение по умолчанию «str». значение по умолчанию не будет указывать на ячейку.

на первой итерации значением по умолчанию будет «птица». Python не будет рассматривать это как free variable . в этом случае мы даже не создаем закрытие, мы создаем функцию. на следующей итерации будет напечатана «рыба», а на последней итерации — «зебра».

Ответ №2:

Я нашел ответ здесь, в блоге Боба Кернера.

 def makeListOfFuncs( strings):
  list = []
  for str in strings:
    def generateFunc( str):
      def echo():
        return str
      return echo
    list.append( generateFunc( str))
  return list

for animalFunc in makeListOfFuncs( ['bird', 'fish', 'zebra']):
  print( animalFunc())
 

Закрытие Python-это странно.