web-dev-qa-db-fra.com

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

Quelqu'un peut-il expliquer les raisons concrètes pour lesquelles BDFL choisit de faire Python lambdas une seule ligne?

C'est bon:

lambda x: x**x

Il en résulte une erreur:

lambda x:
    x**x

Je comprends que la création de plusieurs lignes lambda "perturberait" en quelque sorte les règles normales d'indentation et nécessiterait l'ajout de plus d'exceptions, mais cela ne vaut-il pas les avantages?

Regardez JavaScript, par exemple. Comment vivre sans ces fonctions anonymes? Ils sont indispensables. Les Pythonistes ne veulent-ils pas se débarrasser d'avoir à nommer chaque fonction multi-lignes juste pour la passer en argument?

53
treecoder

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

Mais ces solutions manquent souvent de "Pythonicité" - ce trait insaisissable d'une bonne fonctionnalité Python. Il est impossible d'exprimer la Pythonicité comme une contrainte dure. Même le Zen de Python ne se traduit pas en un simple test de Pythonicité ...

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

Mais je rejette cela aussi, car en fin de compte (et c'est là que j'avoue avoir induit en erreur involontairement le demandeur), je trouve inacceptable toute solution qui intègre 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) également inacceptable, cela fait à peu près un lambda multi-lignes un puzzle insoluble.

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

Fondamentalement, il dit que même si une solution est possible, elle n'est pas conforme à la façon dont Python est.

46
BlackJack

c'est parfaitement bien de faire un lambda multi-lignes en python: voir

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

la véritable limitation lambda est le fait que lambda doit être un seul expression; il ne peut pas contenir de mot-clé (comme print ou return) de python2.

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

25
Vito De Tullio

Je sais que c'est super vieux, mais je le mets ici comme référence.

Une alternative à l'utilisation de lambda pourrait être d'utiliser un def d'une manière non conventionnelle. Le but est de passer un def à une fonction, ce qui ne peut se faire que dans une seule circonstance - un décorateur. Notez qu'avec cette implémentation, def result Ne crée pas de fonction, il crée le résultat de reduce(), qui finit par être dict.

Plug sans vergogne : J'en fais beaucoup 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 lambdas multi-instructions peut être fait, mais seulement avec du code vraiment très laid. Cependant, ce qui est intéressant, c'est le fonctionnement de la portée avec cette implémentation (notez l'utilisation multiple de la variable name et l'occultation de la variable message.

>>> 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!
10
pyrospade

Hacker ensemble un lambda multi-déclarations n'est pas aussi mauvais que le fait pyrospade: sûr que nous pourrions composer un tas de fonctions monadiques en utilisant bind, comme dans Haskell, mais comme 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 le faire 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 instructions , comme print, avec 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 valeur de retour (None, None, None) Géniale, dont nous pouvons ou non nous soucier.

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

= De Python forme une instruction, nous devons donc trouver une expression équivalente. Une façon 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 quelques astuces utilisées dans stateful_lambda:

  • L'argument *_ Permet à notre lambda de prendre n'importe quel nombre d'arguments. Puisque cela autorise zéro arguments, nous récupérons la convention d'appel de stateful_def.
    • Appeler un argument _ Est juste une convention qui dit "je ne vais pas utiliser cette variable"
  • Nous avons une fonction ("wrapper") retournant une autre fonction ("main"): lambda state: lambda *_: ...
    • Grâce à 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 est connu sous le nom de 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 dans state comme des noms de variables et des valeurs liées
    • C'est moins lourd 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 = b Et state.get(a) au lieu de a
  • 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 instruction return

Bien sûr, c'est assez lourd, 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])
4
Warbo

Je pensais pouvoir 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 chose très agréable qui python est capable d'écrire sur des liners, 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é à remplacer 1 fonction entière, je veux dire que vous pouvez le pirater comme (empruntant l'exemple d'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 surtout illisible après un certain temps, c'est comme arriver au début de la corde en commençant par une extrémité effilochée. unraveled rope

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ères de valeurs entières 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 fonction d'expressions de fonctions en définissant une fonction complexe (ou plusieurs et plusieurs lambdas, et en les plaçant dans une 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 superAwesomeFunction et que cette fonction peut faire des choses super géniales, 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 appellerez superAwesomeFunction ou méthode. Si vous effectuez une recherche dans votre dossier Lib, vous pouvez constater que la plupart des python __builtin__ les modules sont écrits de cette façon. Cela se fait parce que parfois vous aurez besoin de certaines fonctions qui effectuent une tâche spécifique qui n'est pas suffisamment nécessaire pour être utilisable par l'utilisateur mais qui est nécessaire pour plusieurs fonctions. Donc, vous avez le choix de ne pas avoir 2 fonctions avec le nom "superAwesomeFunction", vous pouvez avoir "superAwesomeFunctionDoingBasicStuf" et "realSuperAwesomeFunction" et que simplement mettre la "realSuperAwesomeFunction" dans la variable "superAwesomeFunction" et vous avez terminé.

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

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

1
Danilo