2017-10-03 2 views
1

J'ai un tuple de fonctions que je veux pré-charger avec certaines données. Actuellement, la façon dont je fais cela est ci-dessous. Essentiellement, je fais une liste des nouvelles fonctions, et j'y ajoute les fonctions lambda une à la fois, puis reconvertir en un tuple. Cependant, lorsque j'utilise ces fonctions dans une partie différente du code, chacune d'elles agit comme si c'était la dernière dans la liste.Initialisation d'une liste de fonctions lambda en python

def newfuncs(data, funcs): 
    newfuncs = [] 
    for f in funcs: 
     newf = lambda x: f(x, data) 
     newfuncs.append(newf) 
    return tuple(newfuncs) 

Voici un exemple simple du problème

funcs = (lambda x, y: x + y, lambda a, b: a - b) 
funcs = newfuncs(10, funcs) 
print(funcs[0](5)) 
print(funcs[1](5)) 

J'attendre le numéro 15 à imprimer, puis -5. Cependant, ce code imprime le nombre -5 deux fois. Si quelqu'un peut m'aider à comprendre pourquoi cela se produit, ce serait grandement apprécié. Merci!

Répondre

3

Comme mentionné précédemment, la question est à la variable f, qui est la même variable attribuée à touteslambda fonctions, donc à la fin de la boucle, chaquelambda voit la même f.

La solution ici est d'utiliser soit functools.partial, ou créer un argument par défaut scope pour la lambda:

def newfuncs(data, funcs): 
    newfuncs = [] 
    for f in funcs: 
     newf = lambda x, f=f: f(x, data) # note the f=f bit here 
     newfuncs.append(newf) 
    return tuple(newfuncs) 

appeler ces lambda s comme avant donne maintenant:

15 
-5 

Si vous En utilisant python3.x, assurez-vous de jeter un coup d'œil à ce comment by ShadowRanger en tant que caractéristique de sécurité possible pour l'approche par défaut de l'étendue par défaut.

+1

Side-note: Si vous êtes sur Python 3 et que vous utilisez l'approche d'argument par défaut, il est préférable de faire de tels arguments uniquement par mot-clé, donc passer des paramètres de position supplémentaires accidentellement ne remplace pas valeurs (vous ne pouvez les modifier qu'en passant explicitement le nom, 'f' dans ce cas, en tant qu'argument mot-clé). La seule différence est de changer 'lambda x, f = f:' en 'lambda x, *, f = f:', où le '*' (sans nom) fait tous les arguments après le mot-clé seulement. – ShadowRanger

+0

@ShadowRanger Très bien, merci pour cela. –

2

Ceci est un "problème" Python bien connu, ou devrais-je dire "c'est exactement comme Python".

Vous avez créé le tuple:

(x => f(x, data), x => f(x, data)) 

Mais qu'est-ce f? f n'est pas évalué tant que vous n'avez pas appelé les fonctions!

D'abord, f était (x, y)=>x+y. Ensuite, dans votre for -loop, f a été réaffecté à(x, y)=>x-y.

Lorsque vous appelez vos fonctions, la valeur de f sera recherchée. Quelle est la valeur de f à ce stade? La valeur est (x, y)=>x-ypour toutes vos fonctions. Toutes vos fonctions font la soustraction. C'est parce que f est réaffecté. Il n'y a qu'un seul f. Et la valeur de celui-ci et un seul f est définie sur la fonction de soustraction, avant que l'un de vos lambdas ne soit jamais appelé.

ADDENDA

Dans le cas où quelqu'un est intéressé, langues différentes approches de ce problème de différentes manières. JavaScript est plutôt intéressant (certains diraient confus) ici parce qu'il fait des choses en Python, ce que l'OP trouve inattendu, ainsi que d'une manière différente, ce que l'OP aurait attendu. JavaScript:

> let funcs = [] 
> for (let f of [(x,y)=>x+y, (x,y)=>x-y]) { 
    funcs.push(x=>f(x,10)); 
    } 
> funcs[0](5) 
15 
funcs[1](5) 
-5 

Cependant, si vous changez let-var ci-dessus, il se comporte comme Python et vous obtenez -5 pour les deux! En effet, avec let, vous obtenez un f différent pour chaque itération de la boucle for; Avec var, la fonction entière partage le même f, qui ne cesse d'être réaffectée. C'est ainsi que fonctionne Python.

cᴏʟᴅsᴘᴇᴇᴅ vous a montré que la façon de faire ce que vous attendez est de vous assurer d'obtenir que différents f pour chaque itération, qui Python vous permet faire d'une manière assez nette, en défaut le second argument du lambda à un f local à cette itération. C'est plutôt cool, donc leur réponse devrait être acceptée si cela vous aide.

+0

L'exemple javascript est une bonne idée, merci de l'avoir ajouté! –