Требуется создать несколько лямбда-функций внутри выражения list display (оно же list comprehension). Это нужно для DeferredList в Twisted. Пишу примерно так:
return defer.DeferredList([
make_deferred_somehow(j).addCallback(
lambda x: perform_postprocessing(x, i) # это i должно быть каждый раз разным
)
for i, j in get_some_data()
])
Однако получается невесть что — все лямбда-выражения ссылаются на одно и то же значение i. Минимальный пример:
>>> constant = lambda x: lambda: x
>>> [f() for f in [constant(i) for i in range(5)]]
[0, 1, 2, 3, 4] # ожидаемое поведение
>>> [f() for f in [(lambda: i) for i in range(5)]]
[4, 4, 4, 4, 4] # почему все λ привязались к одной и той же i?!
И даже так:
>>> fs = ((lambda: i) for i in range(5))
>>> f0 = next(fs)
>>> f1 = next(fs)
>>> f0()
1
>>> f1()
1
>>> f2 = next(fs)
>>> f0()
2
>>> f1()
2
Пока нашел только один workaround:
>>> [f() for f in [(lambda i=i: i) for i in range(5)]]
[0, 1, 2, 3, 4]
Здравствуйте, Roman Odaisky, Вы писали: RO>А как сделать по-человечески?
А чем тебе не нравится твой вариант с constant? По мне так вполне нормальный.
Не заботится Гвидо о любителях функциональщины. Похоже, компрехеншн переписывается в for, а у for, похоже, переменная цикла создается один раз и потом просто мутируется на каждой итерации. Вот все замыкания и видят эти мутации. В твоем воркэраунде с constant — переменная цикла передается в лямбду в качестве параметра, то бишь копируется — а тебе этого и надо. Что интересно, вокруг подобной ситуации в шарпе неслабый шум поднялся (ключевая фраза — "closing over loop variable").
Re[2]: [python] Привязка переменных в list displays
Здравствуйте, Roman Odaisky, Вы писали: MC>>А чем тебе не нравится твой вариант с constant? По мне так вполне нормальный. RO>Там функции посложнее и каждый раз разные.
Т.е. нужно решение для общего случая: лямбда захватывает несколько мутабельных переменных, надо обеспечить, чтобы их значения были зафиксированы в состоянии на момент создания лямбды и при этом не хочется переписывать сами лямбды? Просто захватываемые переменные можно было бы явно вынести в параметры например, вместо lambda x: f(i, j, k) писать что-то типа (lambda i, j, k: lambda x: f(i, j, k))(i, j, k).
В более другом языке я бы написал макрос, разворачивающийся в вот такую копипасту: (lambda <тут параметры>: <обернутая лямба идет сюда>)(<и тут параметры>). С метапрограмированием на питоне я особо не знаком, но, быть может, найдется способ как-то похачить захваченное лямбдой окружение.
RO>Вообще, насколько я понял, еще ни одному языку не удалось избавиться от указателей, не наставив подобных граблей. То же самое касается типизации...
Мне кажется, дело не в избавлении от указателей, а во внесении функциональщины в язык с мутабельными переменными и мутабельными структурами данных. В в принципе, во всяких scheme тоже можно на подобное напороться — но там мутабельность — это скорее исключение и используется по делу.
Re[2]: [python] Привязка переменных в list displays
Здравствуйте, Mr.Cat, Вы писали:
MC>А чем тебе не нравится твой вариант с constant? По мне так вполне нормальный. MC>Не заботится Гвидо о любителях функциональщины. Похоже, компрехеншн переписывается в for
иное было б странно, т.к. в LC как раз for и пишется..
можно было бы обойти это, выделив для переменной в цикле свой scope и по завершению итерации делать del i .. типа такого
>>> for i in xrange(10):
... lst.append(lambda: i)
... del i
...
>>> lst
[<function <lambda> at 0xb7739a3c>, <function <lambda> at 0xb7739ae4>, <function <lambda> at 0xb773910c>, <function <lambda> at 0xb7739a74>, <function <lambda> at 0xb7739b1c>, <function <lambda> at 0xb7739b54>, <function <lambda> at 0xb7739b8c>, <function <lambda> at 0xb7739bc4>, <function <lambda> at 0xb7739bfc>, <function <lambda> at 0xb7739c34>]
>>> for f in lst: f()
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in <lambda>
NameError: global name 'i'is not defined
правда, тут я не понимаю логики, почему i удалено оказалось.. имхо ссылка на i должна оставаться в лямбде..
RO>return defer.DeferredList([
RO> make_deferred_somehow(j).addCallback(
RO> lambda x: perform_postprocessing(x, i) # это i должно быть каждый раз разным
RO> )
RO> for i, j in get_some_data()
RO>])
RO>
RO>А как сделать по-человечески?
тут подсказали заюзать генераторы.. "простейший" пример начинает выглядеть так:
>>> d = {1: 1, 2: 2, 3: 3}
>>> [(p[1],p[0](123)) for p in ((lambda x: (x,i),j) for i,j in d.items()) ]
[(1, (123, 1)), (2, (123, 2)), (3, (123, 3))]
собственно, главное отличие в том, что вместо вложенного списка создаётся вложенный генератор кортежей вида (лямбда-j)..
в твоём коде это будет выглядь как то так (относительно предыдущего примера только лямбда и x поменялись местами):
return defer.DeferredList([
make_deferred_somehow(gen[0]).addCallback(gen[1])
for gen in ((j, lambda x: perform_postprocessing(x, i)) for i,j in get_some_data())
])