[pyar] La función a la que se le mueve la estantería

Matías Bellone matiasbellone en gmail.com
Vie Jun 7 17:36:02 -03 2019


On Fri, Jun 7, 2019 at 4:56 PM Facundo Batista <facundobatista en gmail.com>
wrote:

> Hola!
>
> Este efecto es conocido:
>
> >>> funcs = [lambda n: n ** i for i in range(3)]
> >>> funcs[0](7)
> 49
>
> Sé como solucionarlo:
>
> >>> funcs = [lambda n, i=i: n ** i for i in range(3)]
> >>> funcs[0](7)
> 1
>
> Pero me interesa escarbar un poquito en por qué sucede. Alguien sabe?
> Idea de por dónde buscar?
>

Originalmente pensé en utilizar ast (disclaimer: primera vez que lo uso)
para ver si por ahí venía la mano:

>>> import ast
>>> a = ast.parse('f = [lambda n: n ** i for i in range(3)]')
>>> ast.dump(a)
"Module(body=[Assign(targets=[Name(id='f', ctx=Store())],
value=ListComp(elt=Lambda(args=arguments(args=[arg(arg='n',
annotation=None)], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None,
defaults=[]), body=BinOp(left=Name(id='n', ctx=Load()), op=Pow(),
right=Name(id='i', ctx=Load()))),
generators=[comprehension(target=Name(id='i', ctx=Store()),
iter=Call(func=Name(id='range', ctx=Load()), args=[Num(n=3)], keywords=[]),
ifs=[], is_async=0)]))])"
>>> b = ast.parse('f = [lambda n, i=i: n ** i for i in range(3)]')
>>> ast.dump(b)
"Module(body=[Assign(targets=[Name(id='f', ctx=Store())],
value=ListComp(elt=Lambda(args=arguments(args=[arg(arg='n',
annotation=None), arg(arg='i', annotation=None)], vararg=None,
kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[Name(id='i',
ctx=Load())]), body=BinOp(left=Name(id='n', ctx=Load()), op=Pow(),
right=Name(id='i', ctx=Load()))),
generators=[comprehension(target=Name(id='i', ctx=Store()),
iter=Call(func=Name(id='range', ctx=Load()), args=[Num(n=3)], keywords=[]),
ifs=[], is_async=0)]))])"

Escribiendo el mail me dí cuenta que mi intento de explicación no tenía
sentido sólo viendo el AST así que pongo lo que se me ocurre que puedo
deducir de lo que ahí dice. La única diferencia es defaults en el Lambda,
por lo que probablemente la ejecución de esa parte del AST genere una copia
local de i en el contexto de ese Lambda en lugar de utilizar el global a
resolver dentro de BinOp. Algo análogo a lo que pasa cuando utilizás
objetos mutables como default en la definición de una función.

O eso me parece.

Saludos,
Toote
------------ próxima parte ------------
Se ha borrado un adjunto en formato HTML...
URL: <http://listas.python.org.ar/pipermail/pyar/attachments/20190607/1850ebfa/attachment.html>


Más información sobre la lista de distribución pyar