Лямбда-функция, выходящая за пределы переменной

Я хотел поиграть с анонимными функциями, поэтому решил сделать простой простой поисковик. Вот он:

tests = []
end = int(1e2)
i = 3
while i <= end:
    a = map(lambda f:f(i),tests)
    if True not in a:
        tests.append(lambda x:x%i==0)
        print i
    print tests
    print "Test: "+str(i)
    print str(a)
    i+=2

То, что я нахожу, заключается в том, что каждый раз обращается к i в lambda x:x%i==0, тогда как я хочу, чтобы это было буквальное число. как я могу заставить его стать lambda x:x%3==0 вместо этого?

Ответы

Ответ 1

Вы можете "захватить" i при создании лямбда

lambda x, i=i: x%i==0

Это установит i в лямбда-контексте, равном какому-либо i, когда он был создан. вы также можете сказать lambda x, n=i: x%n==0, если хотите, это не совсем захватывает, но он получает то, что вам нужно.

Без этого, как вы видели, он будет искать i в охватывающей области


Это проблема поиска, аналогичная следующему с определенными функциями:

i = "original"

def print_i1():
    print(i) # prints "changed" when called below

def print_i2(s=i): #default set at function creation, not call
    print(s) # prints "original" when called below


i = "changed"
print_i1()
print_i2()

Ответ 2

Проблема в том, что каждая из этих функций в tests относится к переменной i.

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

Но здесь он еще проще: i - глобальная переменная, поэтому закрытие отсутствует. Функции скомпилированы для поиска i в качестве глобальной переменной при запуске. Поскольку i изменился, функции будут видеть измененное значение при их запуске. Просто как это.


Традиционный способ (который работает как для закрытия, так и для globals) нежно известен как "взлом по умолчанию", даже если это не хак. (См. объяснение в FAQ). Ответ Райана Хаинга объясняет, как это сделать:

lambda x, i=i: x%i==0

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


По-другому, что может показаться более знакомым, если вы используете такие языки, как JavaScript, - это создать функцию создания функций и передать значение i в качестве аргумента функции создания функции, как в user2864740 Ответ:

(lambda i: lambda x: x%i)(i)

Это позволяет избежать "загрязнения" сигнатуры функции дополнительным параметром (что кто-то может случайно передать аргумент), но ценой создания и вызова функции без уважительной причины.


Третий способ - использовать partial. В тех случаях, когда все, что вы пытаетесь сделать, частично применяет функцию, использование partial вместо определения функции-обертки как lambda может быть более чистым.

К сожалению, в этом случае функция скрыта внутри оператора, а функция operator.mod, которая предоставляет ее, не принимает аргументы ключевого слова, так что вы не можете с пользой частично ее второй операнд. Таким образом, это плохое решение в этом случае. Если вы действительно этого хотели, вы могли бы просто написать обертку, которая ведет себя лучше и partial, что:

def opmod(a, b):
    return a % b

partial(operator.mod, b=i)

В этом случае, я думаю, вам лучше с другими решениями; просто держите это в своей голове для случаев, когда это уместно.

Ответ 3

Создайте новую функцию, которая возвращает лямбда. Затем назовите это, передав i в качестве аргумента. Это создаст новую область привязки.

def make_test (i):
   # this i refers to the parameter (which evaluates to the /value/ passed)
   return lambda x: x%i==0

# ..
# the /value/ resulting from evaluating the variable is passed
tests.append(make_test(i))