Qual è il buon equivalente di python3 per lo spacchettamento automatico della tupla in lambda?


94

Considera il seguente codice python2

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

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

Questo non è supportato in python3 e devo fare quanto segue:

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

Questo è molto brutto. Se il lambda fosse una funzione, potrei farlo

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

La rimozione di questa funzionalità in python3 costringe il codice a fare il brutto modo (con indici magici) o creare funzioni non necessarie (la parte più fastidiosa è pensare a buoni nomi per queste funzioni non necessarie)

Penso che la funzione dovrebbe essere aggiunta di nuovo almeno ai soli lambda. C'è una buona alternativa?


Aggiornamento: sto usando il seguente aiuto che estende l'idea nella risposta

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

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

Update2: una versione più pulita perstar

import functools

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

4
Probabilmente è più probabile lambdache venga rimosso completamente dal linguaggio, quindi invertire le modifiche che lo hanno reso più difficile da usare, ma potresti provare a pubblicare su Python-ideas se desideri esprimere il desiderio di vedere la funzionalità aggiunta di nuovo.
Wooble

3
Non capisco neanche io, ma sembra che il BDFL si opponga lambdacon lo stesso spirito con cui si oppone map, reducee filter.
Cuadue

3
lambdaera previsto per la rimozione in py3k in quanto è fondamentalmente una piaga per la lingua. Ma nessuno poteva concordare un'alternativa adeguata per definire le funzioni anonime, così alla fine Guido alzò le braccia in segno di sconfitta e basta.
roippi

5
le funzioni anonime sono un must in ogni linguaggio appropriato e mi piacciono abbastanza i lambda. Dovrò leggere i perché di un simile dibattito. (Inoltre, anche se mape filtersono meglio sostituiti dalle comprensioni, mi piace reduce)
njzk2

13
L'unica cosa che non mi piace di Python 3 ...
timgeb

Risposte:


33

No, non c'è altro modo. Hai coperto tutto. La strada da percorrere sarebbe sollevare questo problema nella mailing list delle idee di Python , ma sii pronto a discutere molto lì per ottenere un po 'di trazione.

In realtà, solo per non dire "non c'è via d'uscita", un terzo modo potrebbe essere quello di implementare un livello in più di chiamata lambda solo per spiegare i parametri, ma sarebbe allo stesso tempo più inefficiente e più difficile da leggere dei tuoi due suggerimenti:

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

aggiornare Python 3.8

A partire da ora, è disponibile Python 3.8 alpha1 e sono implementate le espressioni di assegnazione PEP 572.

Quindi, se si usa un trucco per eseguire più espressioni all'interno di un lambda - di solito lo faccio creando una tupla e restituendo solo l'ultimo componente di essa, è possibile fare:

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

Si dovrebbe tenere presente che questo tipo di codice raramente sarà più leggibile o pratico che avere una funzione completa. Tuttavia, ci sono possibili usi - se ci sono vari one-line che opererebbero su questo point, potrebbe valere la pena avere una namedtuple, e usare l'espressione di assegnazione per "lanciare" efficacemente la sequenza in arrivo alla namedtuple:

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

3
E, naturalmente, ho dimenticato di dire che il modo "unico ed ovvio" di farlo è spendere effettivamente la funzione di tre righe usando defquella menzionata nella domanda sotto il nme some_name_to_think-of.
jsbueno

2
Questa risposta vede ancora alcuni visitatori: con l'implementazione di PEP 572, dovrebbe esserci un modo per creare variabili all'interno dell'espressione lambda, come in:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno

1
espressioni di assegnazione !! sono (piacevolmente) sorpreso che siano
riusciti

24

Secondo http://www.python.org/dev/peps/pep-3113/, lo spacchettamento delle tuple è sparito e 2to3li tradurrà in questo modo:

Poiché i parametri della tupla vengono usati dai lambda a causa della limitazione della singola espressione, devono essere supportati. Questo viene fatto associando l'argomento della sequenza attesa a un singolo parametro e quindi indicizzandolo su quel parametro:

lambda (x, y): x + y

sarà tradotto in:

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

Che è abbastanza simile alla tua implementazione.


3
Bene che non sia stato rimosso in loop o comprensioni
balki

12

Non conosco nessuna buona alternativa generale al comportamento di spacchettamento degli argomenti di Python 2. Ecco un paio di suggerimenti che potrebbero essere utili in alcuni casi:

  • se non riesci a pensare a un nome; usa il nome del parametro della parola chiave:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
  • potresti vedere se a namedtuplerende il tuo codice più leggibile se l'elenco viene utilizzato in più punti:

    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)

Tra tutte le risposte a questa domanda fino ad ora, usare una namedtuple è la migliore linea d'azione qui. Non solo puoi mantenere il tuo lambda, ma trovo anche che la versione namedtuple sia più leggibile poiché la differenza tra lambda (x, y):e lambda x, y:non è evidente a prima vista.
Burak Arslan

5

Sebbene gli argomenti di destrutturazione siano stati rimossi in Python3, non sono stati rimossi dalle comprensione. È possibile abusarne per ottenere un comportamento simile in Python 3. In sostanza, traiamo vantaggio dal fatto che le co-routine ci consentono di ribaltare le funzioni e il yield non è un'istruzione, e quindi è consentito all'interno di lambda.

Per esempio:

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

In confronto alla risposta accettata di usare un wrapper, questa soluzione è in grado di destrutturare completamente gli argomenti mentre il wrapper destruttura solo il primo livello. Questo è,

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

In confronto a

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

In alternativa si può anche fare

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

O leggermente meglio

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

Questo è solo per suggerire che può essere fatto e non dovrebbe essere preso come una raccomandazione.


0

Sulla base del suggerimento di Cuadue e del tuo commento sullo spacchettamento ancora presente nelle comprensioni, puoi usare, usando numpy.argmin:

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

0

Un'altra opzione è scriverlo in un generatore che produce una tupla in cui la chiave è il primo elemento. Le tuple vengono confrontate dall'inizio alla fine in modo che venga restituita la tupla con il primo elemento più piccolo. È quindi possibile indicizzare nel risultato per ottenere il valore.

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

0

Considera se devi prima decomprimere la tupla:

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

o se è necessario fornire nomi espliciti durante la decompressione:

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

0

Penso che la sintassi migliore sia x * x + y * y let x, y = point, la letparola chiave dovrebbe essere scelta con più attenzione.

Il doppio lambda è la versione più vicina. lambda point: (lambda x, y: x * x + y * y)(*point)

L'helper di funzione di alto ordine sarebbe utile nel caso in cui gli diamo un nome proprio.

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

destruct_tuple(lambda x, y: x * x + y * y)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.