web-dev-qa-db-fra.com

Pas de Multiline Lambda en Python: Pourquoi pas?

J'ai entendu dire que les lambda multilignes ne pouvaient pas être ajoutés en Python, car ils se heurteraient syntaxiquement aux autres constructions syntaxiques de Python. Je pensais à cela dans le bus aujourd’hui et j’ai réalisé que je ne pouvais penser à une seule construction Python avec laquelle les lambdas multilignes s’affrontaient. É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é: quelle est la situation dans laquelle inclure un lambda multiligne serait ambigu? Ce que j'ai entendu est-il vrai ou existe-t-il une autre raison pour laquelle Python n'autorise pas les lambdas multilignes?

243
Imagist

Regardez ce qui suit:

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

S'agit-il d'un lambda renvoyant (y, [1,2,3]) (la carte n'obtient qu'un paramètre, ce qui entraîne une erreur)? Ou retourne-t-il y? Ou est-ce une erreur de syntaxe, parce que 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 des multilignes.

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

125
balpha

Guido van Rossum (l'inventeur de Python) répond lui-même à cette question dans un ancien article de blog .
En gros, 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, à mes yeux: elle nécessite que l'analyseur syntaxique (ou plus précisément, le lexer) soit en mesure de basculer entre les modes sensible au retrait et insensible au retrait, tout en conservant une pile. Techniquement, tout cela peut être résolu (il existe déjà une pile de niveaux d’indentation qui pourraient être généralisés), mais rien de tout cela ne me fait perdre l’intuition que c’est un outil complexe Rube Goldberg . "

517
Eli Courtwright

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

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

Cependant, il n'acceptera aucune affectation et vous devrez donc préparer les données à l'avance .. L'endroit qui m'a semblé le plus utile est l'encapsuleur PySide, où vous avez parfois des rappels courts. Écrire des fonctions de membre 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())
42
Sebastian Bartos

Quelques liens pertinents:

Pendant un moment, je suivais le développement de Reia, qui devait initialement avoir la syntaxe basée sur l'indentation de Python avec des blocs Ruby également, 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 inclut une discussion sur les problèmes qu'il a rencontrés avec l'indentation + blocs multilignes, 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 dans Python que j'ai rencontrés et où Guido envoie une réponse sans réellement l'abattre (je ne sais pas s'il y a eu d'abattage ultérieur, cependant):

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

17
Anon

[Edit] Lire cette réponse. Cela explique pourquoi multiline lambda n’est pas une chose. 

En termes simples, c'est non rythmique. 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 la syntaxe alternative pour le regroupement d'instructions (par exemple, des accolades ou des mots-clés de début/fin) tout aussi inacceptable, cela fait quasiment d'un lambda à plusieurs lignes un casse-tête insoluble.

Pour le reste de cette réponse. Soit utiliser une seule ligne1 lambda ou une fonction nommée. S'il vous plaît ne pas utiliser exec-- Je regrette jamais suggéré cela.

1Vous seriez surpris de ce que vous pouvez faire avec une ligne de python.


Une solution de contournement pour obtenir des fonctions lambda multilignes (une extension de la réponse de skriticos):

(lambda n: (exec('global x; x=4; x=x+n'), x-2)[-1])(0)

Ce qu'il fait:

  • Python simplifie (exécute) tous les composants d’un tuple avant la lecture des délimiteurs.

  • par exemple, lambda x: (functionA(), functionB(), functionC(), 0)[-1] exécutera les trois fonctions même si la seule information utilisée est le dernier élément de la liste (0).

  • Normalement, vous ne pouvez pas affecter ou déclarer des variables dans des listes ou des nuplets en python, mais vous pouvez utiliser la fonction exec (notez qu'elle renvoie toujours: None).

  • Notez que si vous ne déclarez pas une variable en tant que global, elle n’existera pas en dehors de cet appel de fonction exec (cela n’est vrai que pour les fonctions exec dans les instructions lambda).

  • par exemple, (lambda: exec('x=5;print(x)'))() fonctionne correctement sans la déclaration global. Cependant, (lambda: (exec('x=5'), exec('print(x)')))() ou (lambda: (exec('x=5'), x)() ne le font pas.

  • Notez que toutes les variables global sont stockées dans l'espace-noms global et continueront d'exister une fois l'appel de fonction terminé. Pour cette raison, ce n'est pas une bonne solution et doit être évité si possible. Les variables global déclarées à partir de la fonction exec à l'intérieur d'une fonction lambda sont conservées séparément de l'espace de noms global. (testé en Python 3.3.3)

  • Le [-1] à la fin du tuple obtient le dernier index. Par exemple, [1,2,3,4][-1] est 4. Ceci est fait afin que seules les valeurs de sortie souhaitées soient renvoyées au lieu d'un Tuple entier contenant les fonctions None à partir de exec et d'autres valeurs superflues.

Fonction multiligne équivalente:

def function(n):
    x = 4
    x = x+n
    return x-2

function(0)

Façons d'éviter d'avoir besoin d'un lambda multiligne:

Récursion:

f = lambda i: 1 if i==0 or i==1 else f(i-1)+f(i-2)

Les booléens sont des entiers:

lambda a, b: [(0, 9), (2, 3)][a<4][b>3]

Itérateurs:

lambda x: [n**2 for n in x] #Assuming x is a list or Tuple in this case
11
Samy Bencherif

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 formulaire LET en tant que tel:

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

ce qui donne: [0, 3, 8]

8
divs1210

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

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

retourne une fonction qui retourne (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 renvoyant y * z. Ça marche?

6
Wai Yip Tung

(Pour tous ceux toujours intéressés 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
4
vencik

Je suis coupable de pratiquer ce sale tour 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 pourrez trouver un moyen de rester Pythonic, mais si vous devez le faire, cela sera moins pénible que d'utiliser exec et de manipuler des globals.

1
S.Rad

À propos des hacks laids, vous pouvez toujours utiliser une combinaison de exec et une fonction régulière pour définir une fonction multiligne comme celle-ci:

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

Vous pouvez envelopper ceci 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')
1

Je jouais juste un peu pour essayer de faire une compréhension de dictée avec réduire, et arriver avec ce hack one liner:

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 simplement de faire la même chose que ce qui a été fait dans cette compréhension de dictée Javascript: https://stackoverflow.com/a/11068265

0
rodfersou

parce qu'une fonction lambda est supposée être une ligne, c'est la forme la plus simple d'une fonction, an entrance, then return

0
Sphynx-HenryAY