2009-01-17 9 views
24

Je veux créer une liste d'objets lambda à partir d'une liste de constantes en Python; par exemple:Comment créer une liste de lambdas Python (dans une boucle de compréhension de liste/for)?

listOfNumbers = [1,2,3,4,5] 
square = lambda x: x * x 
listOfLambdas = [lambda: square(i) for i in listOfNumbers] 

Cela va créer une liste d'objets lambda, cependant, quand je les lance:

for f in listOfLambdas: 
    print f(), 

j'attendre qu'il imprimerait

1 4 9 16 25 

Au lieu de cela, il estampes:

25 25 25 25 25 

Il semble que les lambdas aient tous reçu le mauvais paramètre. Ai-je fait quelque chose de mal, et y a-t-il un moyen de le réparer? Je suis en Python 2.4 je pense.

EDIT: un peu plus d'essayer des choses et tel est venu avec ceci:

listOfLambdas = [] 
for num in listOfNumbers: 
    action = lambda: square(num) 
    listOfLambdas.append(action) 
    print action() 

Imprime les places attendues de 1 à 25, mais en utilisant l'instruction d'impression plus tôt:

for f in listOfLambdas: 
    print f(), 

me donne toujours tout 25 s. Comment les objets lambda existants ont-ils changé entre ces deux appels d'impression?

question connexe: Why results of map() and list comprehension are different?

Répondre

17

Je devine que le lambda que vous créez dans la compréhension de la liste est liée à la variable i qui finit par se retrouver à 5. Ainsi, lorsque vous évaluez les lambdas après le fait , ils sont tous liés à 5 et finissent par calculer 25. La même chose se passe avec num dans votre second exemple. Lorsque vous évaluez le lambda à l'intérieur de la boucle, son nombre n'a pas changé et vous obtenez la bonne valeur. Après la boucle, num est 5 ...

Je ne suis pas sûr de ce que vous allez faire, donc je ne suis pas sûr de savoir comment proposer une solution. Que dis-tu de ça?

def square(x): return lambda : x*x 
listOfLambdas = [square(i) for i in [1,2,3,4,5]] 
for f in listOfLambdas: print f() 

Cela me donne le résultat attendu:

1 
4 
9 
16 
25 

Une autre façon de penser est qu'un lambda « capture » son environnement lexical d'au point où il est créé. Donc, si vous lui donnez num il ne résout pas réellement cette valeur jusqu'à ce qu'il soit invoqué. C'est à la fois confus et puissant.

0

Je trouve parfois que la définition des classes réelles pour les objets de fonction permet de mieux comprendre ce qui se passe:

>>> class square(object): 
... def __init__(self, val): 
...  self.val = val 
... def __call__(self): 
...  return self.val * self.val 
... 
>>> l = [1,2,3,4,5] 
>>> funcs = [square(i) for i in l] 
>>> for f in funcs: 
... print f() 
... 
1 
4 
9 
16 
25 
>>> 

Certes, il est un peu plus bavard que d'utiliser lambdas ou fermetures, mais je trouve cela plus facile à comprendre quand j'essaie de faire des choses non évidentes avec des fonctions.

18

Vous avez:

listOfLambdas = [lambda: i*i for i in range(6)] 

for f in listOfLambdas: 
    print f() 

Sortie:

25 
25 
25 
25 
25 
25 

Vous avez besoin curryfication! En plus d'être délicieux, utilisez cette valeur par défaut "hack".

listOfLambdas = [lambda i=i: i*i for i in range(6)] 

for f in listOfLambdas: 
    print f() 

Sortie:

0 
1 
4 
9 
16 
25 

Notez le i=i. C'est là que la magie se produit.

+2

Cool. Est-ce que ce "hack" est documenté n'importe où? Y a-t-il une meilleure façon de faire des curry? Veuillez également ne plus mentionner votre lit rotatif. –

+1

Je ne l'ai jamais vu explicitement mentionné dans aucune documentation. Il profite du fait que les valeurs par défaut des paramètres de fonction sont attribuées au moment de la création de la fonction, qui est documentée. – recursive

+1

Cet exemple est presque identique à [Pourquoi les lambdas définis dans une boucle avec des valeurs différentes retournent-ils tous le même résultat?] (Https://docs.python.org/3.4/faq/programming.html#why-do-lambdas- defined-in-a-loop-with-different-values-all-return-the-same-result) à partir des docs. – abarnert

2
listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers] 

Ou

listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers) 
3

Lorsque les instructions de fonction sont exécutés, ils sont liés à leur (lexicalement) renfermant la portée.

Dans votre extrait de code, les lambdas sont liés à la portée globale, car les suites for ne sont pas exécutées comme une unité de portée indépendante en Python. À la fin de la boucle for, le num est lié dans la portée englobante. Démo:

for num in range(1, 6): 
    pass 
assert num == 5 # num is now bound in the enclosing scope 

Ainsi, lorsque vous liez identifiants dans la boucle for vous manipulez en fait la portée englobante.

for num in range(1, 6): 
    spam = 12 
assert num == 5 # num is now bound in the enclosing scope 
assert spam == 12 # spam is also bound in the enclosing scope 

deal Même chose pour la liste compréhensions:

[num for num in range(1, 6)] 
assert num == 5 

souffle de l'esprit, je sais. Anywho, avec nos nouvelles connaissances, nous pouvons déterminer que les lambdas que vous créez se réfèrent à l'identifiant (unique) num lié dans la portée englobante. Cela devrait en faire plus de sens:

functions = [] 
for number in range(1, 6): 
    def fun(): 
     return number 
    functions.append(fun) 
assert all(fun() == 5 for fun in functions) 
assert all(fun() is number for fun in functions) 

Et voici la partie la plus fraîche qui démontre encore plus:

# Same as above -- commented out for emphasis. 
#functions = [] 
#for number in range(1, 6): 
# def fun(): 
#  return number 
# functions.append(fun) 
#assert all(fun() == 5 for fun in functions) 
#assert all(fun() is number for fun in functions) 
number = 6 # Rebind 6 in the scope and see how it affects the results. 
assert all(fun() == 6 for fun in functions) 

donc la solution à cela, bien sûr, est de faire un nouveau champ d'application englobante pour chaque number que vous souhaitez lier. En Python, vous pouvez créer de nouvelles étendues englobantes avec des modules, des classes et des fonctions. Il est courant d'utiliser une fonction uniquement pour créer une nouvelle portée englobante pour une autre fonction.

En Python, une fermeture est une fonction qui renvoie une autre fonction. Un peu comme un constructeur de fonction. Vérifiez get_fun dans l'exemple suivant:

def get_fun(value): 
    """:return: A function that returns :param:`value`.""" 
    def fun(): # Bound to get_fun's scope 
     return value 
    return fun 

functions = [] 
for number in range(1, 6): 
    functions.append(get_fun(number)) 
assert [fun() for fun in functions] == range(1, 6) 

Depuis get_fun est une fonction, il obtient d'avoir son propre champ interne. Chaque fois que vous appelez get_fun avec une valeur, une petite table est créée pour garder trace des liaisons à l'intérieur; c'est-à-dire qu'il est indiqué "Dans ce cadre, l'identificateur value indique la chose qui a été transmise." Cette portée disparaît à la fin de l'exécution de la fonction, à moins qu'il y ait une raison pour que cela traîne.Si vous renvoyez une fonction à l'intérieur d'une étendue, c'est une bonne raison pour que certaines parties de la "table d'étendue" puissent se déplacer - cette fonction que vous renvoyez pourrait référencer des choses de cette table d'étendue quand vous l'appelez plus tard. Pour cette raison, lorsque fun est créé au sein de get_fun Python indique fun sur la table de portée de get_fun, que fun conserve à portée de main lorsque cela est nécessaire.

Vous pouvez en savoir plus sur les détails et la terminologie technique (que j'ai adouci un peu) dans le Python docs on the execution model. Vous pouvez également regarder les parties de la portée englobante auxquelles une fonction fait référence avec print fun.__closure__. Dans ce qui précède, nous voyons la référence au value, qui se trouve être un entier:

# Same as before, commented out for emphasis. 
#functions = [] 
#for number in range(1, 6): 
# functions.append(get_fun(number)) 
#assert [fun() for fun in functions] == range(1, 6) 
print functions[0].__closure__ 
# Produces: (<cell at 0x8dc30: int object at 0x1004188>,) 
1

Essayez d'utiliser() au lieu de []:

listOfLambdas = (lambda: square(i) for i in listOfNumbers) 

Et vous obtiendrez:

1 
4 
9 
16 
25 
0

Vous pouvez également faire:

>>> def squares(): 
...  for i in [1,2,3,4,5]: 
...   yield lambda:i*i 
... 
>>> print [square() for square in squares()] 
[1, 4, 9, 16, 25] 
0

En guise de commentaire supplémentaire, je voudrais souligner la possibilité de générer des listes de fonctions lambda à partir de matrices sympy (je ne sais pas si c'est la meilleure façon de le faire, mais c'est comme ça que je le fais et je le trouve pratique) :

import sympy as sp 
sp.var('Ksi') 
# generate sympy expressions for Berstein's polynomials 
B_expr = sp.Matrix([sp.binomial(3, i) * Ksi**i * (1 - Ksi)**(3-i) for i in range(4)]) 
# lambdify them 
B = [sp.lambdify((Ksi), B_expr[i]) for i in range(4) ] 
Questions connexes