Что такое хороший эквивалент python3 для автоматической распаковки кортежей в лямбда-выражении?

97

Рассмотрим следующий код python2

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

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

Это не поддерживается в python3, и мне нужно сделать следующее:

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

Это очень некрасиво. Если бы лямбда была функцией, я мог бы сделать

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

Удаление этой функции в python3 заставляет код либо действовать некрасиво (с магическими индексами), либо создавать ненужные функции (самая неприятная часть - придумать хорошие имена для этих ненужных функций)

Я думаю, что эту функцию следует добавить хотя бы только к лямбдам. Есть ли хорошая альтернатива?


Обновление: я использую следующий помощник, расширяющий идею в ответе

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

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

Update2: более чистая версия дляstar

import functools

def star(f):
    @functools.wraps(f):
    def f_inner(args):
        return f(*args)
    return f_inner
балки
источник
4
Вероятно, скорее всего lambdaбудет полностью удален из языка, чем отменить изменения, которые усложнили его использование, но вы можете попробовать опубликовать на python-ideas, если вы хотите выразить желание увидеть добавленную функцию.
Wooble
3
Я тоже не понимаю, но похоже, что BDFL противостоит lambdaв том же духе, что и он map, reduceи filter.
Cuadue
3
lambdaбыл намечен для удаления в py3k, так как это в основном вред для языка. Но никто не мог договориться о подходящей альтернативе для определения анонимных функций, поэтому в конце концов Гвидо вскинул руки в поражении, и все.
roippi
5
анонимные функции необходимы на любом правильном языке, и мне очень нравятся лямбды. Я должен прочитать, почему такие дебаты. (Кроме того, хотя mapи filterлучше всего заменены пониманием, мне нравится reduce)
njzk2
13
Единственное, что мне не нравится в Python 3 ...
timgeb

Ответы:

34

Нет, другого выхода нет. Вы все это покрыли. Можно было бы поднять этот вопрос в списке рассылки идей Python , но будьте готовы много спорить там, чтобы получить некоторую поддержку.

На самом деле, просто чтобы не сказать «выхода нет», третий способ может заключаться в реализации еще одного уровня лямбда-вызова, просто чтобы развернуть параметры - но это было бы одновременно более неэффективно и труднее для чтения, чем два ваших предложения:

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

обновить Python 3.8

На данный момент доступен Python 3.8 alpha1 и реализованы выражения присваивания PEP 572-.

Итак, если кто-то использует уловку для выполнения нескольких выражений внутри лямбда-выражения - я обычно делаю это, создавая кортеж и просто возвращая последний его компонент, это можно сделать:

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

Следует иметь в виду, что такой код редко бывает более удобочитаемым или практичным, чем полнофункциональный. Тем не менее, есть возможные применения - если есть различные однострочные, которые будут работать с этим point, может быть полезно иметь namedtuple и использовать выражение присваивания для эффективного «приведения» входящей последовательности к namedtuple:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]
jsbueno
источник
3
И, конечно же, я забыл упомянуть, что «один и тот же очевидный» способ сделать это - это фактически потратить функцию трех строк def, указанную в вопросе под nme some_name_to_think-of.
jsbueno
2
Этот ответ все еще видят некоторые посетители - с реализацией PEP 572 должен быть способ создания переменных внутри лямбда-выражения, например:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno
1
выражения присваивания !! Я (приятно) удивлен, что они
вошли
24

Согласно http://www.python.org/dev/peps/pep-3113/ распаковка кортежей исчезла и 2to3переведет их так:

Поскольку параметры кортежа используются лямбда-выражениями из-за ограничения одного выражения, они также должны поддерживаться. Это делается путем привязки аргумента ожидаемой последовательности к одному параметру и последующей индексации этого параметра:

lambda (x, y): x + y

будет переведен на:

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

Что очень похоже на вашу реализацию.

njzk2
источник
3
Хорошо, что он не удален в цикле for или в пониманиях
balki
12

Я не знаю хороших общих альтернатив распаковке аргументов Python 2. Вот пара предложений, которые могут быть полезны в некоторых случаях:

  • если вы не можете придумать имя; используйте имя параметра ключевого слова:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
    
  • вы можете увидеть, namedtupleделает ли ваш код более читаемым, если список используется в нескольких местах:

    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
источник
Среди всех ответов на этот вопрос до сих пор использование namedtuple - лучший способ действий. Не только вы можете сохранить свою лямбду, но также я считаю, что версия namedtuple более читабельна, поскольку разница между lambda (x, y):и lambda x, y:не очевидна на первый взгляд.
Бурак Арслан
5

Хотя аргументы деструктуризации были удалены в Python3, они не были удалены из понимания. Можно злоупотребить им для получения аналогичного поведения в Python 3. По сути, мы пользуемся преимуществом того факта, что совместные подпрограммы позволяют нам выворачивать функции наизнанку, а yield не является оператором и, следовательно, разрешен в лямбдах.

Например:

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

По сравнению с принятым ответом об использовании оболочки это решение способно полностью разрушить аргументы, в то время как оболочка разрушает только первый уровень. То есть,

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

В сравнении с

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

В качестве альтернативы можно также сделать

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

Или чуть лучше

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

Это всего лишь предположение, что это можно сделать, и не следует воспринимать это как рекомендацию.

рахул
источник
1

Я думаю, что лучше синтаксис x * x + y * y let x, y = point, letключевое слово следует выбирать более тщательно.

Двойная лямбда - ближайшая версия. lambda point: (lambda x, y: x * x + y * y)(*point)

Вспомогательная функция высокого порядка будет полезна, если мы дадим ей собственное имя.

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

destruct_tuple(lambda x, y: x * x + y * y)
anthony.hl
источник
0

Основываясь на предложении Cuadue и вашем комментарии о распаковке, который все еще присутствует в понимания, вы можете использовать, используя numpy.argmin:

result = points[numpy.argmin(x*x + y*y for x, y in points)]
njzk2
источник
0

Другой вариант - записать его в генератор, создав кортеж, в котором ключ является первым элементом. Кортежи сравниваются от начала до конца, поэтому возвращается кортеж с наименьшим первым элементом. Затем вы можете проиндексировать результат, чтобы получить значение.

min((x * x + y * y, (x, y)) for x, y in points)[1]
даз
источник
0

Подумайте, нужно ли вам распаковывать кортеж в первую очередь:

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

или нужно ли указывать явные имена при распаковке:

min(points, key=lambda p: abs(complex(*p))
Чепнер
источник