Анонимные функции, ссылающиеся на локальные переменные в python - PullRequest
7 голосов
/ 27 марта 2012

Как я могу определить анонимные функции в python, где поведение должно зависеть от значения локальной переменной в определенное время, а также принимать аргументы

Пример:

def callback(val1, val2):
   print "{0} {1}".format(val1, val2)

i = 0
f0 = lambda x: callback(i, x)
i = 1
f1 = lambda x: callback(i, x)

f0(8) # prints "1, 8: but I'd like "0, 8" (value of 'i' when f0 was defined)
f1(8) # prints "1, 8"

Возможно ли что-то подобное без упаковки моего обратного вызова в свой класс?

Ответы [ 4 ]

8 голосов
/ 27 марта 2012

Закрытие в python с использованием functools.partial

from functools import partial

i = 0
f0 = partial(callback, i)
i = 1
f1 = partial(callback, i)

f0()
# 0
f1()
# 1

partial похоже на лямбду, но оборачивает значение в этот момент в arg.Не оценивая его при вызове.

Обтекание только некоторых из аргументов

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

def callback(val1, val2):
   print "{0} {1}".format(val1, val2)

i = 0
x = 8
f0 = partial(callback, i)
f0(x)
# 0 8

По сути, вы обернули callback(val1, val2) в callback(val2) с val1, уже включенным в качестве замыкания.

Пример аналогичного эффекта с использованием лямбды

Если вы действительно хотите увидеть, как это сделать с лямбда-замыканием, вы можете понять, почему он становится уродливым, а частичное предпочтительным ...

f0 = (lambda val1: lambda val2: callback(val1, val2))(i)

Вы должны заключить переменную области во внешнюю область функции, а затем сослаться на эту область во внутренней лямбда-функции.ЮК.

Трассировки от исключений: частичное или лямбда-выражение против вложенных функций

Принимая во внимание другие ответы, я подумал, что хотел бы обрисовать еще одну причину использования частичного в отличие от лямбды или внутреннего / внешнегозакрытие функции.Имейте в виду, я имею в виду закрытие функции.functools.partial исправляет трассировку, которую вы получите, когда ваша упакованная функция вызовет исключение ...

Рассмотрим эту версию, которая увеличит деление на ноль:

def callback(val1, val2):
    return val1 / val2

Обычный ауттер/ внутреннее закрытие

def wrapper(fn, val1):
    def wrapped(val2):
            return fn(val1, val2)
    return wrapped

f0 = wrapper(callback, i)
f0(0)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in wrapped
  File "<stdin>", line 2, in callback
ZeroDivisionError: integer division or modulo by zero

лямбда-закрытие

f0 = (lambda val1: lambda val2: callback(val1, val2))(i)
f0(0)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
  File "<stdin>", line 2, in callback
ZeroDivisionError: integer division or modulo by zero

А теперь для functools.partial

f0 = partial(callback, i)
f0(0)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in callback
ZeroDivisionError: integer division or modulo by zero
1 голос
/ 27 марта 2012

Этого можно добиться с помощью functools.partial:

f0 = partial(callback, i)
0 голосов
/ 27 марта 2012

Хотя использование functools.partial является лучшим решением в этом случае, вы также можете создать явные замыкания:

def callback_generator(val):
    def callback():
        return val
    return callback
i = 0
f0 = callback1(i)
i = 1
f1 = callback1(i)

Это также может быть сделано с лямбдами, как показано в комментариях ниже.

0 голосов
/ 27 марта 2012

Вы можете создать функцию, которая создаст функцию. partial - это еще один вариант, упомянутый другими.

def repeater(s):
  def anon():
      print s
  return anon

greet = repeater("Hello, World")
greet()
...