Pas de Lambda multiligne en Python: pourquoi pas?

335

J'ai entendu dire que les lambdas multilignes ne peuvent pas être ajoutés en Python car ils se heurteraient syntaxiquement aux autres constructions de syntaxe en Python. J'y pensais aujourd'hui dans le bus et j'ai réalisé que je ne pouvais pas penser à une seule construction Python avec laquelle les lambdas multilignes se heurtent. Étant donné que je connais assez bien la langue, cela m'a surpris.

Maintenant, je suis sûr que Guido avait une raison de ne pas inclure les lambdas multilignes dans la langue, mais par curiosité: dans quelle situation l'inclusion d'une lambda multilignes serait-elle ambiguë? Ce que j'ai entendu est-il vrai, ou y a-t-il une autre raison pour laquelle Python n'autorise pas les lambdas multilignes?

Imagist
la source
12
tl; dr version: parce que Python est un langage paresseux sans {} blocs et que cela n'était donc pas autorisé afin de conserver une conception syntaxique cohérente.
Andrew
11
Aussi: je suis vraiment surpris que personne n'ait mentionné cela dans les réponses ... Vous pouvez terminer les lignes avec le caractère \ en Python et continuer sur la ligne suivante ... Ces informations remplacent un peu toute cette question donc ...
Andrew
"conception syntaxique"
nicolas
Cela nécessiterait d'autoriser les instructions à l'intérieur des expressions. Si vous allez faire cela, vous n'avez pas besoin d' lambdaexpressions en premier lieu; vous pouvez simplement utiliser des definstructions dans les expressions.
chepner

Réponses:

153

Regardez ce qui suit:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

Est-ce un lambda qui revient (y, [1,2,3])(donc la carte n'obtient qu'un seul paramètre, ce qui entraîne une erreur)? Ou revient-il y? Ou s'agit-il d'une erreur de syntaxe, car la virgule sur la nouvelle ligne est mal placée? Comment Python saurait-il ce que vous voulez?

Dans les parens, l'indentation n'a pas d'importance pour Python, vous ne pouvez donc pas travailler sans ambiguïté avec les multilignes.

Ceci est juste un simple, il y a probablement plus d'exemples.

balpha
la source
107
ils pourraient forcer l'utilisation de parenthèses si vous souhaitez renvoyer un tuple à partir d'un lambda. OMI, cela aurait toujours dû être appliqué pour éviter de telles ambiguïtés, mais bon.
mpen
26
Il s'agit d'une simple ambiguïté qui doit être résolue en ajoutant un ensemble supplémentaire de parens, quelque chose qui existe déjà dans de nombreux endroits, par exemple des expressions de générateur entourées d'autres arguments, appelant une méthode sur un littéral entier (bien que cela ne soit pas nécessairement le cas car un le nom de la fonction ne peut pas commencer par un chiffre), et bien sûr les lambdas d'une seule ligne (qui peuvent être de longues expressions écrites sur plusieurs lignes). Les lambdas à plusieurs lignes ne seraient pas particulièrement différents de ces cas qu'il mérite de les exclure sur cette base. Telle est la vraie réponse.
nmclean
3
J'aime la façon dont il y a des millions de langues qui ne s'en occupent pas, mais d'une manière ou d'une autre, il y a des raisons profondes pour lesquelles c'est soi-disant très difficile, voire impossible
Nicolas
1
@nicolas c'est du python en bref
javadba
Raison pour laquelle je n'utilise pas lambda, donc sous-développé en Python.
NoName
635

Guido van Rossum (l'inventeur de Python) répond lui-même à cette question exacte dans un ancien article de blog .
Fondamentalement, il admet que c'est théoriquement possible, mais que toute solution proposée serait non-Pythonique:

"Mais la complexité de toute solution proposée pour ce casse-tête est immense, pour moi: elle nécessite que l'analyseur (ou plus précisément, le lexer) puisse basculer entre les modes sensibles au retrait et insensibles au retrait, en gardant une pile des modes précédents et du niveau d'indentation. Techniquement, tout cela peut être résolu (il y a déjà une pile de niveaux d'indentation qui pourraient être généralisés). Mais rien de tout cela n'enlève à mon instinct le sentiment que tout cela est un engin élaboré de Rube Goldberg . "

Eli Courtwright
la source
108
Pourquoi n'est-ce pas la meilleure réponse? Il ne s'agit pas de raisons techniques, c'est un choix de conception, comme l'a clairement indiqué l'inventeur.
Dan Abramov
13
@DanAbramov parce que l'OP ne s'est pas connecté depuis des années probablement.
contrat du professeur Falken a été rompu le
7
Pour ceux qui n'ont pas compris la référence Rube Goldberg, voir: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj
56
La réponse de Guido est juste une autre raison pour laquelle je souhaite que Python ne dépende pas de l'indentation pour définir les blocs.
LS
25
Je ne suis pas sûr d'appeler "l'intestin" un choix de conception. ;)
Elliot Cameron
54

C'est généralement très laid (mais parfois les alternatives sont encore plus laides), donc une solution de contournement consiste à faire une expression d'accolades:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

Cependant, il n'acceptera aucune affectation, vous devrez donc préparer les données à l'avance. L'endroit que j'ai trouvé utile est le wrapper PySide, où vous avez parfois de courts rappels. Écrire des fonctions membres supplémentaires serait encore plus moche. Normalement, vous n'en aurez pas besoin.

Exemple:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
Sebastian Bartos
la source
2
Mon patron demandait juste quelque chose comme ça dans notre application PyQt. Impressionnant!
TheGerm
1
Merci pour cela, je cherchais également un bon moyen d'utiliser des lambdas courts (mais toujours multilignes) comme rappels pour notre interface utilisateur PySide.
Michael Leonard
Et maintenant je l'ai vu, il a immédiatement suggéré d'utiliser lambda arget setattr(arg, 'attr','value')de renverser "aucune affectation ...". Et puis il y a l'évaluation des courts-circuits andet or... c'est le Javascript qui le fait. Plonge des racines en vous, comme du lierre dans un mur. J'espère presque que j'oublierai ça à Noël.
nigel222
assez intelligent - et assez lisible. Maintenant - à propos de ces missions (manquantes ..) ..
javadba
@ nigel222 pourquoi avoir honte? Le langage python est fondamentalement paralysé - mais c'est celui utilisé de toute façon pour une grande partie de la science des données . Nous faisons donc des ajustements. Trouver des moyens de faire des effets secondaires (souvent assez simplement l'impression / la journalisation!) Et des affectations (souvent assez pour des variables intermédiaires!) Devraient être bien gérés par la langue. Mais ils ne sont même pas pris en charge (du moins si vous suivez les PEPdirectives)
javadba
17

Quelques liens pertinents:

Pendant un certain temps, je suivais le développement de Reia, qui allait initialement avoir la syntaxe basée sur l'indentation de Python avec des blocs Ruby, le tout au-dessus d'Erlang. Mais, le concepteur a fini par abandonner la sensibilité à l'indentation, et ce post qu'il a écrit sur cette décision comprend une discussion sur les problèmes qu'il a rencontrés avec l'indentation + les blocs multi-lignes, et une appréciation accrue qu'il a acquise pour les problèmes / décisions de conception de Guido:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

En outre, voici une proposition intéressante pour les blocs de style Ruby en Python que j'ai rencontré où Guido publie une réponse sans réellement l'abattre (cependant, je ne suis pas sûr qu'il y ait eu un abattage ultérieur):

http://tav.espians.com/ruby-style-blocks-in-python.html

Anon
la source
12

[Modifier] Lisez cette réponse. Cela explique pourquoi le lambda multiligne n'est pas une chose.

Autrement dit, c'est impythonique. Extrait du blog de Guido van Rossum:

Je trouve inacceptable toute solution qui incorpore un bloc basé sur l'indentation au milieu d'une expression. Étant donné que je trouve une syntaxe alternative pour le regroupement d'instructions (par exemple, accolades ou mots-clés de début / fin) tout aussi inacceptable, cela rend à peu près un lambda multi-lignes un puzzle insoluble.

Samy Bencherif
la source
10

Permettez-moi de vous présenter un hack glorieux mais terrifiant:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

Vous pouvez maintenant utiliser ce LETformulaire comme tel:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

qui donne: [0, 3, 8]

divs1210
la source
1
Publié
divs1210
C'est génial! Je pense que je l'utiliserai la prochaine fois que j'écrirai Python. Je suis principalement un programmeur Lisp et JS, et le manque de lambada multi-lignes fait mal. C'est une façon de l'obtenir.
Christopher Dumas
7

Je suis coupable de pratiquer ce sale hack dans certains de mes projets, ce qui est un peu plus simple:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

J'espère que vous pouvez trouver un moyen de rester pythonique, mais si vous devez le faire, cela est moins douloureux que d'utiliser exec et de manipuler les globaux.

S.Rad
la source
6

Permettez-moi d'essayer de résoudre le problème d'analyse @balpha. J'utiliserais des parenthèses autour de la lamda multiligne. S'il n'y a pas de parenthèses, la définition lambda est gourmande. Donc, le lambda dans

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

renvoie une fonction qui renvoie (y*z, [1,2,3])

Mais

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

veux dire

map(func, [1,2,3])

où func est le lambda multiligne qui renvoie y * z. Est-ce que ça marche?

Wai Yip Tung
la source
1
Je pense que celui du haut devrait revenir map(func, [1,2,3])et celui du bas devrait être une erreur car la fonction map n'a pas assez d'arguments. Il y a aussi des parenthèses supplémentaires dans le code.
Samy Bencherif
en le déposant dans pycharm exécutant python2.7.13, cela donne une erreur de syntaxe.
simbo1905
parenthèses supplémentaires
Samy Bencherif
4

(Pour toute personne encore intéressée par le sujet.)

Considérez ceci (inclut même l'utilisation des valeurs de retour des instructions dans d'autres instructions au sein du lambda "multiligne", bien que ce soit moche au point de vomir ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12
vencik
la source
Cela ne fonctionne pas lors d'un deuxième appel avec des paramètres différents et provoque une fuite de mémoire à moins que la première ligne ne soit state.clear()car les arguments par défaut ne sont créés qu'une seule fois lors de la création de la fonction.
Matthew D. Scholefield
1

Vous pouvez simplement utiliser slash ( \) si vous avez plusieurs lignes pour votre fonction lambda

Exemple:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30
IRSHAD
la source
La question concerne l'utilisation de plus d'une expression plutôt que de plus d'une ligne littérale.
Tomas Zubiri
1

Je commence par python mais venant de Javascript le moyen le plus évident est d'extraire l'expression en fonction ....

Exemple artificiel, l'expression multiplie (x*2)est extraite en fonction et je peux donc utiliser multiligne:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Peut-être que cela ne répond pas exactement à la question si c'était comment faire la multiligne dans l'expression lambda elle - même , mais au cas où quelqu'un obtiendrait ce fil en cherchant comment déboguer l'expression (comme moi), je pense que cela aidera

Vicens Fayos
la source
2
Pourquoi ferais-je cela et pas simplement écrire map(multiply, [1, 2, 3])?
thothal
0

Au sujet des hacks laids, vous pouvez toujours utiliser une combinaison de execet une fonction régulière pour définir une fonction multiligne comme ceci:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

Vous pouvez envelopper cela dans une fonction comme:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')
Matthew D. Scholefield
la source
0

Je jouais juste un peu pour essayer de faire une compréhension de dict avec réduire, et arriver à ce hack d'une ligne:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

J'essayais juste de faire la même chose que ce qui a été fait dans cette compréhension de dict Javascript: https://stackoverflow.com/a/11068265

rodfersou
la source
0

Voici une implémentation plus intéressante des lambdas multi-lignes. Ce n'est pas possible à réaliser à cause de la façon dont python utilise les retraits comme moyen de structurer le code.

Mais heureusement pour nous, le formatage en retrait peut être désactivé à l'aide de tableaux et de parenthèses.

Comme certains l'ont déjà souligné, vous pouvez écrire votre code comme tel:

lambda args: (expr1, expr2,... exprN)

En théorie, si vous êtes assuré d'avoir une évaluation de gauche à droite, cela fonctionnerait, mais vous perdez toujours des valeurs transmises d'une expression à une autre.

Une façon de réaliser ce qui est un peu plus verbeux est d’avoir

lambda args: [lambda1, lambda2, ..., lambdaN]

Où chaque lambda reçoit des arguments de la précédente.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

Cette méthode vous permet d'écrire quelque chose qui ressemble un peu à un lisp / schéma.

Vous pouvez donc écrire des choses comme ceci:

let(lambda x, y: x+y)((1, 2))

Une méthode plus complexe pourrait être utilisée pour calculer l'hypoténuse

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

Cela renverra une liste de nombres scalaires afin qu'il puisse être utilisé pour réduire plusieurs valeurs à un.

Avoir autant de lambda ne sera certainement pas très efficace, mais si vous êtes contraint, cela peut être un bon moyen de faire quelque chose rapidement, puis de le réécrire en tant que fonction réelle plus tard.

Loïc Faure-Lacroix
la source
-3

parce qu'une fonction lambda est censée être unilatérale, car c'est la forme la plus simple d'une fonction, an entrance, then return

Sphynx-HenryAY
la source
1
Non, quiconque écrit un programme événementiel vous dira que le lambda multiligne est important.
Akangka