Функція лямбда в розумінні списку


149

Чому результат наступних двох розумінь списку різний, хоча 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)поверніть той самий тип.


[lambda x: x*x for x in range(10)]швидше, ніж перший, оскільки він не викликає функції зовнішнього циклу, f повторно.
Різа

@Selinap: ... ні, натомість ви створюєте бренд, щойно розширює нову функцію через цикл. ... і накладні витрати на створення цієї нової функції, то дзвінки трохи повільніші (у моїй системі все одно).
Геррат

@Gerrat: Навіть із накладними витратами все одно швидше. Але, звичайно [x*x for x in range(10)], краще.
Різа

34
Я щойно зайшов сюди, щоб отримати доступ до google foobar :)
Gal Margalit

Відповіді:


267

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

Другий не викликає функції. Він створює 10 різних лямбда-функцій. Він заносить усіх до списку. Щоб зробити його рівнозначним першому, вам потрібно:

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

Або ще краще:

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

13
Або map(lambda x: x*x, range(10)), що, мабуть, означало в першу чергу ОП.
Даніель Роузмен

так, лямбда x: x * x .. (x) здається принципом.
staticor

[лямбда x: x * x для x у діапазоні (10)] - це в основному функтур у Haskell
moshe beeri

@DanielRoseman, а якщо точніше list(map(lambda x: x*x, range(10)))дасть вам[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
rrlamichhane

108

Це питання стосується дуже смердючої частини «знаменитого» та «очевидного» синтаксису Python - що має перевагу, лямбда чи розуміння списку.

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

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

Але справа не в цьому. Справа в тому, що W (hy) TF це неоднозначний вираз настільки протиінтуїтивний? І я маю для вас ідіотичний випадок у кінці, тому не відхиляйте мою відповідь занадто рано (у мене це було на співбесіді з роботи).

Отже, розуміння ОП повернуло список лямбдів:

[(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)], або що я вважаю найбільш логічним , це буде list1 елемент - generatorповертає значення. Це просто не так, мова не працює так.

АЛЕ що, якщо ...

Що робити, якщо ви НЕ затьмарюєте forзмінну, а використовуєте її у своїх lambdas?

Ну, тоді трапиться. Подивись на це:

[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'

ВАТ?

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

Зачекайте ... Ми змінюємо кількість аргументів на 1 і переходимо від "занадто багато" до "занадто мало"?

Ну, це не справжній ВАТ, коли ми переходимо 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)

Я б віддав перевагу останню версію над вкладеною лямбда, але перед кожною власною ...


22
це жорстоке і незвичне питання співбесіди.
szeitlin

1
Якби мій колега не запитував, я, мабуть, ніколи б не шукав цієї відповіді
скарбничка

8
Ого. Я просто сильно покусав цією абсурдною поведінкою. Дякую за ваш пост!
Мураха

1
Відмінна відповідь. Я також просто натрапив на це питання. З одного боку, це вказує на обмеження Python, але, з іншого боку, це також може бути індикатором кодового запаху. Я використовую це рішення для іграшкового проекту, але це може бути сигналом до перебудови у виробничому середовищі.
ahota

2
Для ясності та повноти ви можете написати останнє розуміння списку на кшталт:[partial(lambda i, x: i * x, i) for i in (1, 2)]
josoler

19

Велика різниця полягає в тому, що перший приклад насправді викликає лямбда f(x), а другий приклад - ні.

Ваш перший приклад еквівалентний, [(lambda x: x*x)(x) for x in range(10)]тоді як ваш другий приклад еквівалентний [f for x in range(10)].


11

Перший

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

запускається f()для кожного значення в діапазоні, так і f(x)для кожного значення

другий

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

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


11

Люди дали хороші відповіді , але забув згадати найважливішу роль , на мій погляд: У другому прикладі 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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.