Was ist das gute Python3-Äquivalent für das automatische Auspacken von Tupeln in Lambda?


91

Betrachten Sie den folgenden Python2-Code

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

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

Dies wird in Python3 nicht unterstützt und ich muss Folgendes tun:

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

Das ist sehr hässlich. Wenn das Lambda eine Funktion wäre, könnte ich es tun

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

Durch das Entfernen dieser Funktion in Python3 wird der Code gezwungen, entweder die hässliche Methode (mit magischen Indizes) auszuführen oder unnötige Funktionen zu erstellen (am störendsten ist es, sich gute Namen für diese unnötigen Funktionen auszudenken).

Ich denke, die Funktion sollte zumindest nur Lambdas hinzugefügt werden. Gibt es eine gute Alternative?


Update: Ich verwende den folgenden Helfer, um die Idee in der Antwort zu erweitern

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

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

Update2: Eine sauberere Version fürstar

import functools

def star(f):
    @functools.wraps(f):
    def f_inner(args):
        return f(*args)
    return f_inner

4
Es ist wahrscheinlich wahrscheinlicher lambda, dass Sie vollständig aus der Sprache entfernt werden, als Änderungen rückgängig zu machen, die die Verwendung erschwert haben. Sie können jedoch versuchen, auf Python-Ideen zu posten, wenn Sie den Wunsch äußern möchten, die Funktion wieder hinzuzufügen.
Wooble

3
Ich verstehe es auch nicht, aber es scheint, als ob der BDFL sich lambdaim gleichen Geist wie er widersetzt map, reduceund filter.
Cuadue

3
lambdawurde für die Entfernung in py3k geplant, da es im Grunde eine Plage für die Sprache ist. Aber niemand konnte sich auf eine geeignete Alternative für die Definition anonymer Funktionen einigen, also warf Guido schließlich niedergeschlagen die Arme hoch und das war's.
Roippi

5
Anonyme Funktionen sind ein Muss in jeder richtigen Sprache, und ich mag die Lambdas sehr. Ich muss das Warum einer solchen Debatte lesen. (Auch wenn mapund filteram besten durch Verständnis ersetzt werden, mag ich reduce)
njzk2

13
Das einzige, was ich an Python 3 nicht
mag

Antworten:


32

Nein, es gibt keinen anderen Weg. Du hast alles abgedeckt. Der Weg wäre, dieses Problem auf die Python-Ideen-Mailingliste zu setzen , aber seien Sie bereit, dort viel zu streiten, um etwas Traktion zu gewinnen.

Um nicht zu sagen, dass es keinen Ausweg gibt, könnte ein dritter Weg darin bestehen, eine weitere Ebene von Lambda-Aufrufen zu implementieren, um nur die Parameter zu entfalten - aber das wäre gleichzeitig ineffizienter und schwerer zu lesen als Ihre beiden Vorschläge:

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

Aktualisieren Sie Python 3.8

Ab sofort ist Python 3.8 alpha1 verfügbar und PEP 572-Zuweisungsausdrücke sind implementiert.

Wenn man also einen Trick verwendet, um mehrere Ausdrücke in einem Lambda auszuführen - normalerweise mache ich das, indem ich ein Tupel erstelle und nur die letzte Komponente davon zurückgebe, ist es möglich:

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

Man sollte bedenken, dass diese Art von Code selten lesbarer oder praktischer ist als eine volle Funktion. Dennoch gibt es mögliche Verwendungszwecke - wenn es verschiedene Einzeiler gibt, die damit arbeiten würden point, könnte es sich lohnen, ein benanntes Tupel zu haben und den Zuweisungsausdruck zu verwenden, um die eingehende Sequenz effektiv in das benannte Tupel "umzuwandeln":

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]

3
Und natürlich habe ich vergessen zu erwähnen, dass der "eine und offensichtliche" Weg, dies zu tun, darin besteht, die Drei-Zeilen-Funktion tatsächlich mit der defin der Frage unter der Nummer genannten zu verwenden some_name_to_think-of.
Jsbueno

2
Diese Antwort sehen immer noch einige Besucher - mit der Implementierung von PEP 572 sollte es eine Möglichkeit geben, Variablen innerhalb des Lambda-Ausdrucks zu erstellen, wie in:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno

1
Zuweisungsausdrücke !! Ich bin (angenehm) überrascht, dass sie es geschafft haben
Javadba

23

Laut http://www.python.org/dev/peps/pep-3113/ sind das Auspacken von Tupeln weg und 2to3werden sie wie folgt übersetzen:

Da Lambdas aufgrund der Beschränkung auf einen einzelnen Ausdruck Tupelparameter verwenden, müssen sie ebenfalls unterstützt werden. Dazu wird das erwartete Sequenzargument an einen einzelnen Parameter gebunden und anschließend für diesen Parameter indiziert:

lambda (x, y): x + y

wird übersetzt in:

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

Welches ist ziemlich ähnlich zu Ihrer Implementierung.


3
Gut, dass es nicht für Schleife oder Verständnis entfernt wird
Balki

11

Ich kenne keine guten allgemeinen Alternativen zum Entpackungsverhalten der Python 2-Argumente. Hier sind einige Vorschläge, die in einigen Fällen nützlich sein können:

  • wenn Ihnen kein Name einfällt; Verwenden Sie den Namen des Schlüsselwortparameters:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
  • Sie können sehen, ob a namedtupleIhren Code lesbarer macht, wenn die Liste an mehreren Stellen verwendet wird:

    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)

Unter all den bisherigen Antworten auf diese Frage ist die Verwendung eines benannten Tupels die beste Vorgehensweise. Sie können nicht nur Ihr Lambda behalten, sondern ich finde auch, dass die NamedTuple-Version besser lesbar ist als der Unterschied zwischen lambda (x, y):und lambda x, y:auf den ersten Blick nicht offensichtlich ist.
Burak Arslan

5

Während die Destrukturierungsargumente in Python3 entfernt wurden, wurden sie nicht aus dem Verständnis entfernt. Es ist möglich, es zu missbrauchen, um ein ähnliches Verhalten in Python 3 zu erhalten. Im Wesentlichen nutzen wir die Tatsache, dass Co-Routinen es uns ermöglichen, Funktionen auf den Kopf zu stellen, und Yield ist keine Aussage und daher innerhalb von Lambdas zulässig.

Beispielsweise:

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))))

Im Vergleich zur akzeptierten Antwort der Verwendung eines Wrappers kann diese Lösung die Argumente vollständig zerstören, während der Wrapper nur die erste Ebene zerstört. Das ist,

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))))

Im Vergleich zu

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

Alternativ kann man auch machen

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

Oder etwas besser

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

Dies soll nur darauf hinweisen, dass dies möglich ist und nicht als Empfehlung verstanden werden sollte.


0

Basierend auf dem Cuadue-Vorschlag und Ihrem Kommentar zum Auspacken, der immer noch im Verständnis vorhanden ist, können Sie Folgendes verwenden numpy.argmin:

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

0

Eine andere Möglichkeit besteht darin, es in einen Generator zu schreiben, der ein Tupel erzeugt, bei dem der Schlüssel das erste Element ist. Tupel werden von Anfang bis Ende verglichen, sodass das Tupel mit dem kleinsten ersten Element zurückgegeben wird. Sie können dann in das Ergebnis indizieren, um den Wert zu erhalten.

min((x * x + y * y, (x, y)) for x, y in points)[1]

0

Überlegen Sie, ob Sie das Tupel zuerst auspacken müssen:

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

oder ob Sie beim Auspacken explizite Namen angeben müssen:

min(points, key=lambda p: abs(complex(*p))

0

Ich denke, die bessere Syntax ist x * x + y * y let x, y = point, dass das letSchlüsselwort sorgfältiger ausgewählt werden sollte.

Das doppelte Lambda ist die nächstgelegene Version. lambda point: (lambda x, y: x * x + y * y)(*point)

Ein Funktionshelfer hoher Ordnung wäre nützlich, wenn wir ihm einen richtigen Namen geben.

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

destruct_tuple(lambda x, y: x * x + y * y)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.