Quel est le bon équivalent python3 pour le décompactage automatique de tuple dans lambda?

94

Considérez le code python2 suivant

In [5]: points = [ (1,2), (2,3)]

In [6]: min(points, key=lambda (x, y): (x*x + y*y))
Out[6]: (1, 2)

Cela n'est pas pris en charge dans python3 et je dois faire ce qui suit:

>>> min(points, key=lambda p: p[0]*p[0] + p[1]*p[1])
(1, 2)

C'est très moche. Si le lambda était une fonction, je pourrais faire

def some_name_to_think_of(p):
  x, y = p
  return x*x + y*y

La suppression de cette fonctionnalité dans python3 oblige le code à faire la moche (avec des index magiques) ou à créer des fonctions inutiles (la partie la plus ennuyeuse est de penser à de bons noms pour ces fonctions inutiles)

Je pense que la fonctionnalité devrait être ajoutée au moins aux lambdas seuls. Y a-t-il une bonne alternative?


Mise à jour: j'utilise l'aide suivante pour étendre l'idée dans la réponse

def star(f):
  return lambda args: f(*args)

min(points, key=star(lambda x,y: (x*x + y*y))

Update2: une version plus propre pourstar

import functools

def star(f):
    @functools.wraps(f):
    def f_inner(args):
        return f(*args)
    return f_inner
balki
la source
4
Il est probablement plus probable lambdad'être complètement supprimé du langage que d'annuler les modifications qui le rendaient plus difficile à utiliser, mais vous pouvez essayer de publier sur python-ideas si vous souhaitez exprimer le désir de voir la fonctionnalité ajoutée.
Wooble
3
Je ne comprends pas non plus, mais il semble que le BDFL s'oppose lambdadans le même esprit qu'il s'oppose map, reduceet filter.
Cuadue
3
lambdadevait être supprimé dans py3k car il s'agit essentiellement d'un fléau pour la langue. Mais personne ne pouvait s'entendre sur une alternative appropriée pour définir les fonctions anonymes, alors Guido a finalement jeté les bras en signe de défaite et c'était tout.
roippi
5
les fonctions anonymes sont indispensables dans toutes les langues appropriées, et j'aime assez les lambdas. Je vais devoir lire le pourquoi d'un tel débat. (Aussi, même si mapet filtersont mieux remplacés par des compréhensions, j'aime bien reduce)
njzk2
13
La seule chose que je n'aime pas à propos de Python 3 ...
timgeb

Réponses:

33

Non, il n'y a pas d'autre moyen. Vous avez tout couvert. La voie à suivre serait de soulever cette question sur le liste de diffusion des idées Python , mais soyez prêt à discuter beaucoup là-bas pour gagner du terrain.

En fait, pour ne pas dire "il n'y a pas d'issue", une troisième façon pourrait être d'implémenter un niveau supplémentaire d'appel lambda juste pour déplier les paramètres - mais ce serait à la fois plus inefficace et plus difficile à lire que vos deux suggestions:

min(points, key=lambda p: (lambda x,y: (x*x + y*y))(*p))

mettre à jour Python 3.8

A partir de maintenant, Python 3.8 alpha1 est disponible et les expressions d'affectation PEP 572 sont implémentées.

Donc, si on utilise une astuce pour exécuter plusieurs expressions à l'intérieur d'un lambda - je le fais généralement en créant un tuple et en renvoyant simplement le dernier composant, il est possible de faire:

>>> a = lambda p:(x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
>>> a((3,4))
25

Il faut garder à l'esprit que ce type de code sera rarement plus lisible ou plus pratique que d'avoir une fonction complète. Pourtant, il y a des utilisations possibles - s'il y a plusieurs one-liners qui opéreraient là point-dessus, cela pourrait valoir la peine d'avoir un namedtuple et d'utiliser l'expression d'affectation pour effectivement "transtyper" la séquence entrante vers le namedtuple:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]
jsbueno
la source
3
Et bien sûr, j'ai oublié de mentionner que la manière "unique et évidente" de faire ceci est de dépenser réellement la fonction de trois lignes en utilisant ce defqui est mentionné dans la question sous le nme some_name_to_think-of.
jsbueno
2
Cette réponse voit encore quelques visiteurs - avec l'implémentation PEP 572, il devrait y avoir moyen de créer des variables à l'intérieur de l'expression lambda, comme dans:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno
1
expressions d'affectation !! Je suis (agréablement) surpris qu'ils aient réussi
javadba
24

Selon http://www.python.org/dev/peps/pep-3113/, le déballage des tuple a disparu et 2to3les traduira comme suit:

Comme les paramètres de tuple sont utilisés par les lambdas en raison de la limitation de l'expression unique, ils doivent également être pris en charge. Cela se fait en ayant l'argument de séquence attendu lié à un seul paramètre, puis en indexant ce paramètre:

lambda (x, y): x + y

sera traduit en:

lambda x_y: x_y[0] + x_y[1]

Ce qui est assez similaire à votre implémentation.

njzk2
la source
3
Bien qu'il ne soit pas supprimé dans la boucle for ou les compréhensions
balki
12

Je ne connais pas de bonnes alternatives générales au comportement de décompression des arguments de Python 2. Voici quelques suggestions qui pourraient être utiles dans certains cas:

  • si vous ne pouvez pas penser à un nom; utilisez le nom du paramètre de mot-clé:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
  • vous pouvez voir si a namedtuplerend votre code plus lisible si la liste est utilisée à plusieurs endroits:

    from collections import namedtuple
    from itertools import starmap
    
    points = [ (1,2), (2,3)]
    Point = namedtuple('Point', 'x y')
    points = list(starmap(Point, points))
    
    result = min(points, key=lambda p: p.x**2 + p.y**3)
jfs
la source
Parmi toutes les réponses à cette question jusqu'à présent, l'utilisation d'un namedtuple est le meilleur plan d'action ici. Non seulement vous pouvez garder votre lambda, mais je trouve également que la version namedtuple est plus lisible car la différence entre lambda (x, y):et lambda x, y:n'est pas évidente à première vue.
Burak Arslan
5

Bien que les arguments de déstructuration aient été supprimés dans Python3, ils n'ont pas été supprimés des compréhensions. Il est possible d'en abuser pour obtenir un comportement similaire en Python 3. En substance, nous profitons du fait que les co-routines nous permettent de retourner les fonctions à l'envers, et yield n'est pas une instruction, et est donc autorisé dans lambdas.

Par exemple:

points = [(1,2), (2,3)]
print(min(points, key=lambda y: next(x*x + y*y for x,y in (lambda a: (yield a))(y))))

En comparaison avec la réponse acceptée d'utiliser un wrapper, cette solution est capable de déstructurer complètement les arguments tandis que le wrapper ne déstructure que le premier niveau. C'est,

values = [(('A',1),'a'), (('B',0),'b')]
print(min(values, key=lambda y: next(b for (a,b),c in (lambda x: (yield x))(y))))

En comparaison à

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda p: (lambda a,b: (lambda x,y: (y))(*a))(*p)))

Alternativement, on peut aussi faire

values = [(('A',1),'a'), (('B',0),'b')]
print(min(points, key=lambda y: next(b for (a,b),c in [y])))

Ou légèrement mieux

print(min(values, key=lambda y: next(b for ((a,b),c) in (y,))))

Il s'agit simplement de suggérer que cela peut être fait et ne doit pas être considéré comme une recommandation.

rahul
la source
0

Sur la base de la suggestion de Cuadue et de votre commentaire sur le déballage toujours présent dans les compréhensions, vous pouvez utiliser, en utilisant numpy.argmin:

result = points[numpy.argmin(x*x + y*y for x, y in points)]
njzk2
la source
0

Une autre option est de l'écrire dans un générateur produisant un tuple où la clé est le premier élément. Les tuples sont comparés du début à la fin afin que le tuple avec le premier élément le plus petit soit renvoyé. Vous pouvez ensuite indexer le résultat pour obtenir la valeur.

min((x * x + y * y, (x, y)) for x, y in points)[1]
daz
la source
0

Déterminez si vous devez décompresser le tuple en premier lieu:

min(points, key=lambda p: sum(x**2 for x in p))

ou si vous devez fournir des noms explicites lors de la décompression:

min(points, key=lambda p: abs(complex(*p))
chepner
la source
0

Je pense que la meilleure syntaxe est x * x + y * y let x, y = point, le letmot - clé devrait être choisi plus soigneusement.

Le double lambda est la version la plus proche. lambda point: (lambda x, y: x * x + y * y)(*point)

Un assistant de fonction d'ordre élevé serait utile au cas où nous lui donnerions un nom correct.

def destruct_tuple(f):
  return lambda args: f(*args)

destruct_tuple(lambda x, y: x * x + y * y)
anthony.hl
la source