Подтвердить что ты не робот

Лямбда-функция в списках

Почему выходные данные следующих двух представлений списка отличаются, даже если f и lambda функция одинаковы?

f = lambda x: x*x
[f(x) for x in range(10)]

а также

[lambda x: x*x for x in range(10)]

Напоминаем, что и type(f) и type(lambda x: x*x) возвращают один и тот же тип.

4b9b3361

Ответ 1

Первый создает одну лямбда-функцию и называет ее десять раз.

Вторая функция не вызывает функцию. Он создает 10 различных лямбда-функций. Он помещает все из списка. Чтобы сделать его эквивалентным первому, вам нужно:

[(lambda x: x*x)(x) for x in range(10)]

Или еще лучше:

[x*x for x in range(10)]

Ответ 2

Этот вопрос затрагивает очень вонючую часть "известного" и "очевидного" синтаксиса Python - то, что имеет приоритет, лямбда, или для понимания списка.

Я не думаю, что целью ОП было создание списка квадратов от 0 до 9. Если бы это было так, мы могли бы дать еще больше решений:

squares = []
for x in range(10): squares.append(x*x)
  • это хороший старый способ императивного синтаксиса.

Но это не главное. Дело в том, что W (hy) TF - это неоднозначное выражение, настолько нелогичное? И в конце у меня для вас идиотский случай, так что не отклоняйте мой ответ слишком рано (он был у меня на собеседовании).

Итак, OP-понимание вернуло список лямбд:

[(lambda x: x*x) for x in range(10)]

Это, конечно, всего 10 различных копий функции возведения в квадрат, см.:

>>> [lambda x: x*x for _ in range(3)]
[<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]

Обратите внимание, адреса памяти лямбд - все они разные!

Конечно, у вас может быть более "оптимальная" (ха-ха) версия этого выражения:

>>> [lambda x: x*x] * 3
[<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]

См? 3 раза одна и та же лямбда.

Обратите внимание, что я использовал _ в качестве переменной for. Это не имеет ничего общего с x в lambda (оно омрачено лексически!). Получите это?

Я опускаю обсуждение, почему приоритет синтаксиса не таков, что все это означало:

[lambda x: (x*x for x in range(10))]

который может быть: [[0, 1, 4, ..., 81]], или [(0, 1, 4, ..., 81)], или , который я считаю наиболее логичным, это будет list из 1 элемента - generator, возвращающий значения. Это просто не тот случай, язык так не работает.

НО Что, если...

Что если вы не затеняете переменную for И не используете ее в своих lambda???

Ну, тогда дерьмо случается. Посмотрите на это:

[lambda x: x * i for i in range(4)]

это означает, конечно:

[(lambda x: x * i) for i in range(4)]

НО это не значит:

[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]

Это просто безумие!

Лямбды в понимании списка являются закрытием по всему объему этого понимания. лексическое замыкание, поэтому они ссылаются на i через ссылку, а не на его значение при оценке!

Итак, это выражение:

[(lambda x: x * i) for i in range(4)]

примерно эквивалентно:

[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]

Я уверен, что мы могли бы увидеть больше здесь, используя декомпилятор Python (я имею в виду, например, модуль dis), но для обсуждения, не связанного с Python-VM, этого достаточно. Вот вам и вопрос для собеседования.

Теперь, как сделать list лямбда-множителей, которые действительно умножаются на последовательные целые числа? Итак, аналогично принятому ответу, нам нужно разорвать прямую связь с i, обернув его в другое lambda, которое вызывается внутри выражения понимания списка:

Перед тем:

>>> a = [(lambda x: x * i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
2

После:

>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)]
>>> a[1](1)
2
>>> a[0](1)
1

(у меня была и внешняя лямбда-переменная = i, но я решил, что это более ясное решение - я представил y, чтобы мы все могли видеть, какая ведьма какая).

Изменить 2019-08-30:

Следуя предложению @josoler, которое также присутствует в ответе @sheridp, значение "петлевой переменной" для понимания списка может быть "встроено" в объект - ключ для доступа к нему в нужное время. Раздел "После" выше делает это, оборачивая его в другой lambda и вызывая его немедленно с текущим значением i. Другой способ (немного проще для чтения - он не производит эффекта 'WAT') - сохранить значение i внутри объекта partial, а "внутренний" (оригинальный) lambda воспринимать его как аргумент (передается предоставленным объектом partial во время вызова), то есть:

После 2:

>>> from functools import partial
>>> a = [partial(lambda y, x: y * x, i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Отлично, но есть еще маленький поворот для вас! Допустим, мы не хотим облегчать чтение кода и передаем фактор по имени (в качестве ключевого аргумента partial). Давайте сделаем переименование:

После 2.5:

>>> a = [partial(lambda coef, x: coef * x, coef=i) for i in (1, 2)]
>>> a[0](1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() got multiple values for argument 'coef'

WAT?

>>> a[0]()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

Подождите... Мы меняем число аргументов на 1 и переходим от "слишком много" к "слишком мало"?

Ну, это не настоящая WAT, когда мы передаем coef в partial таким образом, он становится ключевым аргументом, поэтому он должен следовать после позиционного аргумента x, например, так:

После 3:

>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)]
>>> a[0](2), a[1](2)
(2, 4)

Я бы предпочел последнюю версию над вложенной лямбдой, но каждому свою...

Ответ 3

Большая разница заключается в том, что первый пример действительно вызывает lambda f(x), а второй пример - нет.

Ваш первый пример эквивалентен [(lambda x: x*x)(x) for x in range(10)], а ваш второй пример эквивалентен [f for x in range(10)].

Ответ 4

Первый

f = lambda x: x*x
[f(x) for x in range(10)]

запускает f() для каждого значения в диапазоне, поэтому f(x) для каждого значения

второй

[lambda x: x*x for x in range(10)]

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

Ответ 5

Люди дали хорошие ответы, но забыл упомянуть о самой важной части, на мой взгляд: Во втором примере X понимания списка НЕ ​​совпадает с X функции lambda, они полностью не связаны. Таким образом, второй пример на самом деле такой же, как:

[Lambda X: X*X for I in range(10)]

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

С другой стороны, первый пример работает совершенно иначе, поскольку X итераций DO взаимодействует с результатами, для каждой итерации значение X*X, поэтому результат будет [0,1,4,9,16,25, 36, 49, 64 ,81]

Ответ 6

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

import functools
a = [functools.partial(lambda x: x*x, x) for x in range(10)]

b = []
for i in a:
    b.append(i())

In [26]: b
Out[26]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Пока этот пример надуман, я нашел его полезным, когда мне нужен список функций, каждый из которых печатает что-то другое, т.е.

import functools
a = [functools.partial(lambda x: print(x), x) for x in range(10)]

for i in a:
    i()