Создание функции itertools.product с неожиданными результатами
У меня возникли небольшие проблемы с пониманием результатов приведенного ниже фрагмента, и я думаю, что это потому, что я запутался в привязке функций. Почему следующие фрагменты дают разные результаты?
import itertools
def make_funcs(lst):
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
yield f
## examples:
for f in make_funcs(range(2)):
print(f.func_name, f())
## prints:
>0 9
>1 8
## works as expected:
for f in make_funcs(range(2)):
for g in make_funcs(range(2)):
print(f.func_name, g.func_name, f() + g())
## prints:
>0 >0 18
>0 >1 17
>1 >0 17
>1 >1 16
в то время как с другой стороны:
## provides results that are counter-intuitive (to me, at least)
for f, g in itertools.product(make_funcs(range(2)), make_funcs(range(2))):
print(f.func_name, g.func_name, f() + g())
## prints:
>0 >0 16
>0 >1 16
>1 >0 16
>1 >1 16
Мне кажется, что это только захват / использование /binding
последняя переменная в каждом неявном for
- цикл для вычисления, даже если он выбирает правильную переменную для имен функций.
Что мне не хватает в определениях области действия или определениях функций или замыканиях (или что-то еще), которые вызывают эти результаты?
NB: если какой-либо из тегов, которые я поставил на этот вопрос, не имеет значения, не стесняйтесь удалять их - я помещаю их все, потому что я не уверен, в чем проблема.
2 ответа
Все функции по-прежнему ссылаются на переменную val
,
def make_funcs(lst):
a = []
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
a.append(f)
return a
В результате все отпечатки становятся "не интуитивными".
def make_funcs(lst):
a = []
for val in lst:
def f():
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
a.append(f)
val = 10
return a
Всегда приводит к 0.
Однако, поскольку вы используете генератор, значение val
изменяется только после того, как он был использован, так что, кажется, все хорошо. При использовании itertools.product, документы говорят, что это делает это:
def product(*args, repeat=1):
# product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
# product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
pools = [tuple(pool) for pool in args] * repeat
result = [[]]
for pool in pools:
result = [x+[y] for x in result for y in pool]
for prod in result:
yield tuple(prod)
Это означает, что он сначала перебирает оба генератора (эффективно изменяя значение val
четыре раза) и только потом вычисляет результаты.
Это все происходит потому, что val
определяется в объеме make_funcs
(а не в объеме f
), поэтому, если второй вызов генератора изменяет значение val
все функции ссылаются на новое значение.
Изменить: Пожалуйста, также прочитайте ответ @newacct
Другой ответ объясняет, почему вы видите то, что видите - это потому, что функции захватывают внешние переменные по ссылке, и то же самое val
Переменная фиксируется несколькими функциями, которые видят изменения в переменной.
Чтобы добавить, если вы хотите избежать этого, если вы хотите захватить по значению, один из способов сделать это - использовать параметр и аргумент по умолчанию во внутренней функции:
def make_funcs(lst):
for val in lst:
def f(val=val):
return sum(1 for i in range(10) if i > val)
f.func_name = ">" + str(val)
yield f