Pourquoi Python n'autorise-t-il pas les lambdas multilignes?

50

Quelqu'un peut-il expliquer les raisons concrètes pour lesquelles BDFL a choisi de créer une ligne unique de Python lambdas?

C'est bon:

lambda x: x**x

Cela entraîne une erreur:

lambda x:
    x**x

Je comprends que faire lambda multi-line "dérangerait" les règles d'indentation normales et nécessiterait l'ajout d'exceptions supplémentaires, mais est-ce que cela ne vaut pas les avantages?

Regardez JavaScript, par exemple. Comment peut-on vivre sans ces fonctions anonymes? Ils sont indispensables. Les pythonistes ne veulent-ils pas éviter d'avoir à nommer chaque fonction multiligne pour la passer en argument?

treecoder
la source
3
Étant donné que vous notez les raisons concrètes pour lesquelles Guido n'autorise pas les lambdas à expressions multiples et les rejette ensuite, je vais supposer que vous recherchez une validation plutôt qu'une réponse réelle.
Jason Baker
3
Mis à part la sauvegarde de sept personnages, en quoi est-ce mieux qu'un def? Il a maintenant exactement la même structure visuelle.
Détly

Réponses:

42

Guido van van Rossum a répondu lui-même:

Mais de telles solutions manquent souvent de "Pythonicity" - ce trait insaisissable d’une bonne fonctionnalité Python. Il est impossible d'exprimer Pythonicity comme une contrainte difficile. Même le Zen de Python ne se traduit pas par un simple test de Pythonicity ...

Dans l'exemple ci-dessus, il est facile de trouver le talon d'Achille de la solution proposée: le double-colon, bien que syntaxiquement non ambigu (l'une des "contraintes du puzzle"), est complètement arbitraire et ne ressemble à rien d'autre en Python ...

Mais je rejette cela aussi, car à la fin (et c’est là que j’admets d’avoir induit en erreur le déposant), 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.

http://www.artima.com/weblogs/viewpost.jsp?thread=147358

En gros, il dit que même si une solution est possible, elle n’est pas conforme à la réalité de Python.

BlackJack
la source
2
+1 merci pour le lien de fil - mais j'apprécierais toujours les lambdas multilignes - ils sont inestimables - regardez JavaScript, PHP les inclut également.
Treecoder
2
@greengit Vous pouvez simplement utiliser un def imbriqué. Ce n'est pas la même chose que les fonctions anonymes, mais elles sont assez proches.
Jsternberg
2
les définitions imbriquées ne permettent pas de passer des fonctions en arguments - c'est la raison numéro un pour laquelle je voudrais des lambds multilignes
treecoder
1
@greengit - Je pense que vous feriez mieux de parler de cela avec GvR que de poster vos commentaires ici.
Jason Baker
9
@greengit: Vous savez que vous pouvez transmettre la fonction en tant qu'argument à une autre fonction? Vous ne pouvez pas l'écrire en ligne, mais il n'y a pas de technique de programmation qui ne soit disponible.
Octobre
24

c'est parfaitement bien de faire un lambda multiligne en python: voir

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

la vraie limitation de lambda est le fait que lambda doit être une expression unique ; il ne peut pas contenir de mot clé (comme python2 printou return).

GvR a choisi de le faire pour limiter la taille du lambda, car ils sont normalement utilisés comme paramètres. Si vous voulez une vraie fonction, utilisezdef

Vito De Tullio
la source
1
multi line concerne l’insertion du caractère '\ n': D python n’a pas l ’ instruction multiple lambda. Vous voulez vraiment utiliser def. Pensez-y: vous avez vraiment besoin d'un appelable comme paramètre de votre fonction? Et les utilisateurs de cette fonction ne sont pas autorisés à transmettre votre appelable par défaut? Comment peuvent-ils le passer si vous ne le leur donnez pas?
Vito De Tullio
Au fait, pouvez-vous donner un exemple de votre besoin d'une fonction anonyme?
Vito De Tullio
1
Oui, je trouve la limitation d'une seule expression vraiment frustrante. Certes, s’ils autorisent les lambdas multi-expressions, les gens vont certainement commencer à en abuser, mais l’inverse est trop restrictif.
rbaleksandar
10

Je sais que c'est très vieux, mais en prenant ici comme référence.

Une alternative à l'utilisation de lambda pourrait être d'utiliser une defméthode non conventionnelle. Le but est de passer defà une fonction, ce qui ne peut être fait que dans un seul cas - un décorateur. Notez que cette implémentation def resultne crée pas de fonction, mais crée le résultat reduce()qui finit par être un dict.

Prise Shameless : Je fais beaucoup de cela ici .

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

Notez que les lambdas multi-instructions peuvent être utilisés, mais uniquement avec du code vraiment, vraiment moche. Cependant, ce qui est intéressant, c’est la manière dont la portée fonctionne avec cette implémentation (notez l’utilisation multiple de la namevariable et l’observation de la messagevariable.

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!
pyrospade
la source
+1 pour une approche monadique
mercredi
Les monades sont aussi appelées thenables ou futures / promesses ou même rappels dans JavaScript BTW.
aoeu256
3

Pirater ensemble un lambda à déclarations multiples n'est pas si mal que le dit pyrospade: nous pourrions composer un tas de fonctions monadiques en utilisant bind, comme dans Haskell, mais puisque nous sommes dans le monde impur de Python, nous pourrions aussi bien utiliser des effets secondaires pour obtenir la même chose.

Je couvre quelques façons de faire cela sur mon blog .

Par exemple, Python garantit d'évaluer les éléments d'un tuple dans l'ordre, afin que nous puissions utiliser un ,peu comme un impératif ;. Nous pouvons remplacer de nombreuses déclarations, comme print, par des expressions, comme sys.stdout.write.

Par conséquent, les éléments suivants sont équivalents:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

Notez que j'ai ajouté un Noneà la fin et que je l'ai extrait à l'aide de [-1]; cela définit explicitement la valeur de retour. Nous n’avons pas à le faire, mais sans cela, nous obtiendrions une (None, None, None)valeur de retour géniale , qui pourrait nous intéresser ou non.

Nous pouvons donc séquencer les actions IO. Qu'en est-il des variables locales?

Python =forme une déclaration, nous devons donc trouver une expression équivalente. Une solution consiste à muter le contenu de la structure de données, passé en argument. Par exemple:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

Il y a peu de trucs utilisés dans stateful_lambda:

  • L' *_argument permet à notre lambda de prendre n'importe quel nombre d'arguments. Puisque cela ne permet aucun argument, nous récupérons la convention d'appel de stateful_def.
    • L'appel d'un argument _est juste une convention qui dit "je ne vais pas utiliser cette variable"
  • Nous avons une fonction ("wrapper") renvoyant une autre fonction ("main"): lambda state: lambda *_: ...
    • Grâce à la portée lexicale , l'argument de la première fonction sera dans la portée de la deuxième fonction
    • Accepter certains arguments maintenant et renvoyer une autre fonction pour accepter le reste plus tard s'appelle currying
  • Nous appelons immédiatement la fonction "wrapper" en lui passant un dictionnaire vide: (lambda state: ...)({})
    • Cela nous permet d'affecter une variable stateà une valeur {}sans utiliser une instruction d'affectation (par exemple state = {}) .
  • Nous traitons les clés et les valeurs en statetant que noms de variables et valeurs liées
    • C'est moins encombrant que d'utiliser des lambdas immédiatement appelés
    • Cela nous permet de muter les valeurs des variables
    • Nous utilisons state.setdefault(a, b)au lieu de a = bet state.get(a)au lieu dea
  • Nous utilisons un tuple pour enchaîner nos effets secondaires, comme avant
  • Nous utilisons [-1]pour extraire la dernière valeur, qui agit comme une returndéclaration

Bien sûr, cela est assez fastidieux, mais nous pouvons créer une API plus agréable avec des fonctions d'assistance:

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the `get` and `setdefault` methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh `get` and `setdefault`
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses `get` and `set`(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])
Warbo
la source
tu plaisantes, cela ressemble à un cauchemar lol
Alexander Mills
1
@AlexanderMills Heh, cela ne devait pas être un exemple concret, mais plutôt une réfutation de l'approche lambdas-in-lambdas-in-lambdas de pyrospade, pour montrer que les choses ne sont pas si mauvaises. En fait, ceci pourrait être simplifié beaucoup plus loin maintenant que nous avons python.org/dev/peps/pep-0572
Warbo
1

Si je pouvais contribuer, utilisez un disjoncteur:

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

N'oubliez pas la très belle chose que python est capable d'écrire sur les marqueurs, comme dans l'exemple:

a=b=0; c=b+a; d = a+b**2 #etc etc

Et le lambda est très puissant, mais il n’est pas destiné à la substitution d’une fonction entière, je veux dire que vous pourriez le pirater comme (exemple emprunté à un collègue ci-dessus):

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

Mais voulez-vous vraiment le faire comme ça? Il est généralement illisible au bout d’un certain temps, c’est comme aller au début de la corde en commençant par la fin non déchirée. corde déchirée

Les Lambdas sont des fonctions uniques, dans la carte, filtrent et réduisent les fonctions dans la programmation orientée fonctionnelle (entre autres). Par exemple, obtenir des valeurs de caractère de valeurs qui sont des entiers et divisibles par 2

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = map( chrDev2, range(30) )
>>> ['\x00', 1, '\x02', 3, '\x04', 5, '\x06', 7, '\x08', 9, '\n', 11, '\x0c', 13, '\x0e', 15, '\x10', 17, '\x12', 19, '\x14', 21, '\x16', 23, '\x18', 25, '\x1a', 27, '\x1c', 29]

Vous pouvez l'utiliser comme expression de fonction pour diviser la fonction complexe (ou plusieurs et plusieurs lambdas, et l'insérer dans un autre lambda:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

mais Python prend en charge les expressions de fonction d'une autre manière: les -lets disent que vous avez une fonction appelée superAwesomeFunctionet que cette fonction peut faire des choses super impressionnantes, vous pouvez l'affecter à une variable en ne l'appelant pas, comme ceci:

SAF = superAwesomeFunction # there is no () at the end, 

Alors maintenant, lorsque vous appelez SAF, vous appelez superAwesomeFunction ou sa méthode. Si vous effectuez une recherche dans votre dossier Lib, vous constaterez que la plupart des __builtin__modules python sont écrits de cette façon. Ceci est fait parce que parfois vous aurez besoin de fonctions qui font une tâche spécifique qui n’est pas assez nécessaire pour être utilisable par l’utilisateur mais qui est nécessaire pour plusieurs fonctions. Donc vous avez le choix. Vous ne pouvez pas avoir 2 fonctions portant le nom "superAwesomeFunction", vous pouvez avoir "superAwesomeFunctionDoingBasicStuf" et "realSuperAwesomeFunction" et ne mettre que la variable "realSuperAwesomeFunction" dans la variable "superAwesomeFunction" et vous avez terminé.

Vous pouvez trouver l'emplacement des modules importés en entrant dans console importedModule.__file__(exemple réel import os;os.__file__), puis suivez ce répertoire dans le fichier importsModule.py et ouvrez-le dans l'éditeur pour trouver comment vous pouvez maximiser vos propres "connaissances".

J'espère que cela vous aidera, vous et d'autres collègues en difficulté, peut-être.

Danilo
la source