Qual é o bom equivalente em python3 para descompactação automática de tupla em lambda?

92

Considere o seguinte código python2

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

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

Isso não é compatível com python3 e tenho que fazer o seguinte:

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

Isso é muito feio. Se o lambda fosse uma função, eu poderia fazer

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

Remover esse recurso em python3 força o código a fazer o caminho feio (com índices mágicos) ou a criar funções desnecessárias (a parte mais incômoda é pensar em bons nomes para essas funções desnecessárias)

Acho que o recurso deve ser adicionado de volta pelo menos para lambdas sozinho. Existe uma boa alternativa?


Atualização: estou usando o seguinte auxiliar, estendendo a ideia na resposta

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

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

Update2: uma versão mais limpa parastar

import functools

def star(f):
    @functools.wraps(f):
    def f_inner(args):
        return f(*args)
    return f_inner
Balki
fonte
4
É provavelmente mais provável lambdaque seja removido da linguagem completamente do que reverter as alterações que a tornaram mais difícil de usar, mas você pode tentar postar em ideias-python se quiser expressar o desejo de ver o recurso adicionado de volta.
Wooble
3
Eu também não entendi, mas parece que o BDFL se opõe lambdano mesmo espírito que ele se opõe map, reducee filter.
Cuadue de
3
lambdafoi programado para remoção no py3k, pois é basicamente uma praga na linguagem. Mas ninguém conseguia chegar a um acordo sobre uma alternativa adequada para definir funções anônimas, então, eventualmente, Guido jogou as armas na derrota e foi isso.
roippi
5
funções anônimas são obrigatórias em qualquer linguagem apropriada, e eu gosto bastante dos lambdas. Terei que ler os porquês de tal debate. (Além disso, embora mape filtersejam melhor substituídos por compreensões, eu gosto reduce)
njzk2
13
A única coisa que não gosto no Python 3 ...
timgeb

Respostas:

32

Não, não há outra maneira. Você cobriu tudo. O caminho a seguir seria levantar essa questão na lista de discussão de ideias do Python , mas esteja preparado para discutir muito lá para ganhar um pouco de força.

Na verdade, só para não dizer "não há saída", uma terceira maneira poderia ser implementar mais um nível de chamada lambda apenas para desdobrar os parâmetros - mas isso seria ao mesmo tempo mais ineficiente e mais difícil de ler do que suas duas sugestões:

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

atualizar Python 3.8

A partir de agora, o Python 3.8 alpha1 está disponível e as expressões de atribuição PEP 572 estão implementadas.

Então, se alguém usa um truque para executar várias expressões dentro de um lambda - geralmente faço isso criando uma tupla e apenas retornando o último componente dela, é possível fazer:

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

Deve-se ter em mente que esse tipo de código raramente será mais legível ou prático do que ter uma função completa. Ainda assim, há usos possíveis - se houver vários one-liners que operariam nisso point, pode valer a pena ter um namedtuple e usar a expressão de atribuição para efetivamente "lançar" a sequência de entrada para o namedtuple:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]
jsbueno
fonte
3
E, claro, esqueci de mencionar que a maneira "única e óbvia" de fazer isso é realmente passar a função de três linhas usando o defque é mencionado na pergunta sob o nme some_name_to_think-of.
jsbueno
2
Esta resposta ainda vê alguns visitantes - com a implementação do PEP 572, deve haver uma maneira de criar variáveis ​​dentro da expressão lambda, como em:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno
1
expressões de atribuição !! Estou (agradavelmente) surpreso por eles terem chegado em
javadba
23

De acordo com http://www.python.org/dev/peps/pep-3113/ a descompactação de tupla acabou e 2to3irá traduzi-la da seguinte forma:

Como os parâmetros de tupla são usados ​​por lambdas devido à limitação de expressão única, eles também devem ser suportados. Isso é feito tendo o argumento de sequência esperado vinculado a um único parâmetro e, em seguida, indexando nesse parâmetro:

lambda (x, y): x + y

será traduzido para:

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

Que é bastante semelhante à sua implementação.

njzk2
fonte
3
É bom que não seja removido no loop for ou compreensões
balki
11

Não conheço nenhuma boa alternativa geral para o comportamento de desempacotamento dos argumentos do Python 2. Aqui estão algumas sugestões que podem ser úteis em alguns casos:

  • se você não consegue pensar em um nome; use o nome do parâmetro de palavra-chave:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
  • você pode ver se um namedtupletorna seu código mais legível se a lista for usada em vários lugares:

    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
fonte
Entre todas as respostas para esta pergunta até agora, usar um namedtuple é o melhor curso de ação aqui. Não apenas você pode manter seu lambda, mas também acho que a versão nomeada dupla é mais legível, pois a diferença entre lambda (x, y):e lambda x, y:não é óbvia à primeira vista.
Burak Arslan
5

Embora os argumentos de desestruturação tenham sido removidos no Python3, eles não foram removidos das compreensões. É possível abusar dele para obter um comportamento semelhante no Python 3. Em essência, aproveitamos o fato de que as co-rotinas nos permitem virar as funções do avesso, e o rendimento não é uma instrução e, portanto, é permitido dentro dos lambdas.

Por exemplo:

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

Em comparação com a resposta aceita de usar um invólucro, esta solução é capaz de desestruturar completamente os argumentos, enquanto o invólucro destrói apenas o primeiro nível. Isso é,

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

Em comparação a

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

Alternativamente, também se pode fazer

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

Ou um pouco melhor

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

Isso é apenas para sugerir que isso pode ser feito e não deve ser tomado como uma recomendação.

Rahul
fonte
0

Com base na sugestão do Cuadue e em seu comentário sobre o desempacotamento ainda estando presente nas compreensões, você pode usar numpy.argmin:

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

Outra opção é escrevê-lo em um gerador produzindo uma tupla onde a chave é o primeiro elemento. As tuplas são comparadas do início ao fim para que a tupla com o menor primeiro elemento seja retornada. Você pode então indexar no resultado para obter o valor.

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

Considere se você precisa descompactar a tupla em primeiro lugar:

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

ou se você precisa fornecer nomes explícitos ao desempacotar:

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

Acho que a melhor sintaxe é x * x + y * y let x, y = point, a letpalavra-chave deve ser escolhida com mais cuidado.

O lambda duplo é a versão mais próxima. lambda point: (lambda x, y: x * x + y * y)(*point)

O auxiliar de função de alta ordem seria útil caso lhe atribuíssemos um nome próprio.

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

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