Понимание списка Python с lambdas
Я запускаю Python 3.4.2, и я запутался в поведении моего кода. Я пытаюсь создать список вызываемых полиномиальных функций с возрастающей степенью:
bases = [lambda x: x**i for i in range(3)]
Но почему-то он делает это:
print([b(5) for b in bases])
# [25, 25, 25]
Почему bases
, по-видимому, повторяется список последнего лямбда-выражения, в понимании списка?
Ответы
Ответ 1
Проблема, которая представляет собой classic
"gotcha" ,
что i
, на который ссылаются в лямбда-функциях, не просматривается, пока
лямбда-функция называется. В это время значение i
является последним значением it
был связан с завершением for-loop
, т.е. 2
.
Если вы привязываете i
к значению по умолчанию в определении функций lambda
, то каждый i
становится локальной переменной, а его значение по умолчанию оценивается и привязывается к функции во время лямбда а не называется.
Таким образом, когда вызывается лямбда, теперь i
просматривается в локальной области и используется значение по умолчанию:
In [177]: bases = [lambda x, i=i: x**i for i in range(3)]
In [178]: print([b(5) for b in bases])
[1, 5, 25]
Для справки:
Ответ 2
более "питонический" подход:
используя вложенные функции:
def polyGen(degree):
def degPolynom(n):
return n**degree
return degPolynom
polynoms = [polyGen(i) for i in range(5)]
[pol(5) for pol in polynoms]
выход:
→ [1, 5, 25, 125, 625]
Ответ 3
В качестве альтернативного решения вы можете использовать частичную функцию:
>>> bases = [(lambda i: lambda x: x**i)(i) for i in range(3)]
>>> print([b(5) for b in bases])
[1, 5, 25]
Единственное преимущество этой конструкции над классическим решением, данное @unutbu, заключается в том, что вы не можете вводить скрытые ошибки, вызывая вашу функцию с неправильным количеством аргументов:
>>> print([b(5, 8) for b in bases])
# ^^^
# oups
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
TypeError: <lambda>() takes 1 positional argument but 2 were given
Как было предложено Адамом Смитом в комментарии ниже, вместо использования "вложенной лямбды" вы можете использовать functools.partial
с той же выгодой:
>>> import functools
>>> bases = [functools.partial(lambda i,x: x**i,i) for i in range(3)]
>>> print([b(5) for b in bases])
[1, 5, 25]
>>> print([b(5, 8) for b in bases])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <listcomp>
TypeError: <lambda>() takes 2 positional arguments but 3 were given
Ответ 4
Я не думаю, что вопрос "почему это происходит" еще был дан ответ.
Причина, по которой имена нелокальных имен в функции не считаются константами, заключается в том, что эти нелокальные имена будут соответствовать поведению глобальных имен. То есть, изменения в глобальном имени после создания функции наблюдаются при вызове функции.
например.
# global context
n = 1
def f():
return n
n = 2
assert f() == 2
# non-local context
def f():
n = 1
def g():
return n
n = 2
assert g() == 2
return g
assert f()() == 2
Вы можете видеть, что в глобальном и нелокальном контекстах, если значение имени изменяется, то это изменение отражается в будущих вызовах функции, которая ссылается на имя. Если бы глобальные и нелокальные отношения обрабатывались по-разному, это было бы путать. Таким образом, поведение становится последовательным. Если вам нужно текущее значение имени для создания константы для новой функции, то идиоматическим способом является делегирование создания функции другой функции. Функция создается в области создания-функции (где ничего не меняется), и, следовательно, значение имени не изменится.
например.
def create_constant_getter(constant):
def constant_getter():
return constant
return constant_getter
getters = [create_constant_getter(n) for n in range(5)]
constants = [f() for f in getters]
assert constants == [0, 1, 2, 3, 4]
Наконец, в качестве дополнения функции могут изменять нелокальные имена (если имя помечено как таковое), так же как они могут изменять глобальные имена. например.
def f():
n = 0
def increment():
nonlocal n
n += 1
return n
return increment
g = f()
assert g() + 1 == g()