Чим хороший еквівалент python3 для автоматичного розпакування кортежу в лямбда-програмі?


94

Розглянемо наступний код 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-idea, якщо хочете висловити бажання повернути цю функцію.
Вубл

3
Я теж не розумію, але, здається, BDFL виступає проти lambdaтого ж духу, що і він map, reduceі filter.
Куадуе

3
lambdaбуло заплановано видалити в py3k, оскільки це в основному шкідливо для мови. Але ніхто не міг домовитись про належну альтернативу для визначення анонімних функцій, тому врешті-решт Гвідо розправив руки, і це було все.
roippi

5
анонімні функції є обов’язковими для використання у будь-якій належній мові, і мені дуже подобаються лямбди. Мені доведеться прочитати, чому така дискусія. (Крім того, хоч mapі filterїх найкраще замінити розуміннями, мені це подобається reduce)
njzk2

13
Єдине, що мені не подобається в Python 3 ...
timgeb

Відповіді:


33

Ні, іншого шляху немає. Ви все це покрили. Шляхом цього було б підняти цю проблему в списку розсилки ідей 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]

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]

Що цілком схоже на вашу реалізацію.


3
Добре, що його не видаляють для циклу або
розумінь

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)

Серед усіх відповідей на це запитання на сьогодні найкращим методом дій є використання імені. Ви не тільки можете зберегти свою лямбду, але і я переконався, що названа назва версії є більш читабельною, оскільки різниця між 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,))))

Це лише натякає на те, що це можна зробити, і не слід сприймати як рекомендацію.


0

На основі пропозиції Cuadue та вашого коментаря щодо розпакування, який все ще присутній у програмах, ви можете використовувати, використовуючи numpy.argmin:

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

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

0

Я думаю, що кращий синтаксис 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.