Jaki jest dobry odpowiednik Pythona3 dla automatycznego rozpakowywania krotek w lambdzie?


97

Rozważmy następujący kod python2

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

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

Nie jest to obsługiwane w python3 i muszę wykonać następujące czynności:

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

To jest bardzo brzydkie. Gdyby lambda była funkcją, mógłbym to zrobić

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

Usunięcie tej funkcji w pythonie3 wymusza na kodzie brzydkie postępowanie (z magicznymi indeksami) lub tworzenie niepotrzebnych funkcji (najbardziej kłopotliwe jest wymyślenie dobrych nazw dla tych niepotrzebnych funkcji)

Myślę, że ta funkcja powinna zostać dodana przynajmniej do samych lambd. Czy jest dobra alternatywa?


Aktualizacja: używam następującego pomocnika rozszerzającego pomysł w odpowiedzi

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

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

Update2: czystsza wersja dlastar

import functools

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

4
Prawdopodobnie bardziej prawdopodobne lambdajest całkowite usunięcie z języka, a następnie cofnięcie zmian, które utrudniły korzystanie z niego, ale możesz spróbować opublikować posty na temat Pythona, jeśli chcesz wyrazić chęć ponownego dodania funkcji.
Wooble

3
Ja też tego nie rozumiem, ale wygląda na to, że BDFL sprzeciwia się lambdaw tym samym duchu, co on map, reducei filter.
Cuadue

3
lambdazostał przeznaczony do usunięcia w py3k, ponieważ jest to w zasadzie plaga języka. Ale nikt nie mógł zgodzić się na właściwą alternatywę dla definiowania anonimowych funkcji, więc w końcu Guido rzucił w górę ramiona w porażce i to wszystko.
roippi

5
Funkcje anonimowe są obowiązkowe w każdym odpowiednim języku, a ja bardzo lubię lambdy. Będę musiał przeczytać powody takiej debaty. (Również, choć mapi filtersą najlepiej zastąpione listowe, lubię reduce)
njzk2

13
Jedna rzecz, której nie lubię w Pythonie 3 ...
timgeb

Odpowiedzi:


34

Nie, nie ma innego wyjścia. Zakryłaś to wszystko. Najlepszym sposobem byłoby poruszenie tego problemu na liście dyskusyjnej pomysłów na Python , ale bądź przygotowany na wiele dyskusji, aby zyskać trochę przyczepności.

Właściwie, żeby nie powiedzieć „nie ma wyjścia”, trzecim sposobem mogłoby być zaimplementowanie jeszcze jednego poziomu wywołań lambda tylko w celu rozwinięcia parametrów - ale byłoby to jednocześnie mniej wydajne i trudniejsze do odczytania niż twoje dwie sugestie:

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

zaktualizuj Python 3.8

Obecnie dostępny jest język Python 3.8 alpha1, a wyrażenia przypisania PEP 572 są zaimplementowane.

Tak więc, jeśli ktoś używa sztuczki do wykonania wielu wyrażeń wewnątrz lambdy - zwykle robię to, tworząc krotkę i po prostu zwracając jej ostatni składnik, można to zrobić:

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

Należy pamiętać, że ten rodzaj kodu rzadko będzie bardziej czytelny lub praktyczny niż posiadający pełną funkcję. Nadal istnieją możliwe zastosowania - jeśli istnieją różne pointjednolinijkowe, które by na tym działały , warto byłoby mieć namedtuple i użyć wyrażenia przypisania, aby skutecznie „rzutować” przychodzącą sekwencję na nazwaną tukę:

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

3
I oczywiście zapomniałem wspomnieć, że „jedynym i oczywistym” sposobem na to jest faktyczne wykorzystanie funkcji trzech wierszy, o defktórej mowa w pytaniu pod nazwą some_name_to_think-of.
jsbueno

2
Ta odpowiedź wciąż widzi niektórych odwiedzających - przy implementacji PEP 572 powinien istnieć sposób na tworzenie zmiennych wewnątrz wyrażenia lambda, na przykład:key = lambda p: (x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
jsbueno

1
wyrażenia przypisania !! jestem (miło) zaskoczony, że udało im się
javadba

24

Zgodnie z http://www.python.org/dev/peps/pep-3113/ rozpakowywanie krotek zniknęło i 2to3przetłumaczy je w następujący sposób:

Ponieważ parametry krotki są używane przez wyrażenia lambda z powodu ograniczenia pojedynczego wyrażenia, muszą być również obsługiwane. Odbywa się to poprzez powiązanie oczekiwanego argumentu sekwencji z pojedynczym parametrem, a następnie indeksowanie tego parametru:

lambda (x, y): x + y

zostanie przetłumaczone na:

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

Co jest dość podobne do twojej implementacji.


3
Dobrze, że nie jest usuwany w pętli lub zrozumieniach
balki

12

Nie znam żadnych dobrych ogólnych alternatyw dla zachowania podczas rozpakowywania argumentów Pythona 2. Oto kilka sugestii, które mogą być przydatne w niektórych przypadkach:

  • jeśli nie możesz wymyślić imienia; użyj nazwy parametru słowa kluczowego:

    def key(p): # more specific name would be better
        x, y = p
        return x**2 + y**3
    
    result = min(points, key=key)
    
  • możesz zobaczyć, czy namedtuplepoprawi czytelność kodu, jeśli lista jest używana w wielu miejscach:

    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)
    

Spośród wszystkich dotychczasowych odpowiedzi na to pytanie, najlepszym sposobem postępowania jest tutaj użycie nazwanej trzykrotności. Nie tylko możesz zachować swoją lambdę, ale także uważam, że nazwana wersja podwójna jest bardziej czytelna, ponieważ różnica między lambda (x, y):i lambda x, y:nie jest oczywista na pierwszy rzut oka.
Burak Arslan

5

Chociaż argumenty destrukturyzujące zostały usunięte w Pythonie3, nie zostały one usunięte ze zrozumień. Można go nadużywać w celu uzyskania podobnego zachowania w Pythonie 3. W istocie korzystamy z faktu, że współprogramy pozwalają nam odwrócić funkcje na lewą stronę, a yield nie jest instrukcją, a zatem jest dozwolony w lambdach.

Na przykład:

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

W porównaniu z akceptowaną odpowiedzią używania wrappera, rozwiązanie to jest w stanie całkowicie zniszczyć argumenty, podczas gdy wrapper niszczy tylko pierwszy poziom. To jest,

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

W stosunku do

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

Alternatywnie można to zrobić

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

Albo trochę lepiej

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

Ma to tylko zasugerować, że można to zrobić i nie należy tego traktować jako zalecenia.


1

Myślę, że im lepsza składnia x * x + y * y let x, y = point, letsłowo kluczowe powinno być starannie dobrane.

Najbliższą wersją jest podwójna lambda. lambda point: (lambda x, y: x * x + y * y)(*point)

Pomocnik funkcji wysokiego rzędu przydałby się na wypadek, gdybyśmy nadali jej właściwą nazwę.

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

destruct_tuple(lambda x, y: x * x + y * y)

0

Opierając się na sugestii Cuadue i komentarzu na temat rozpakowywania, który nadal jest obecny w zrozumieniu, możesz użyć, używając numpy.argmin:

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

0

Inną opcją jest zapisanie go w generatorze tworzącym krotkę, w której klucz jest pierwszym elementem. Krotki są porównywane od początku do końca, więc zwracana jest krotka z najmniejszym pierwszym elementem. Następnie możesz zindeksować wynik, aby uzyskać wartość.

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

0

Zastanów się, czy w pierwszej kolejności musisz rozpakować krotkę:

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

lub czy musisz podać wyraźne nazwy podczas rozpakowywania:

min(points, key=lambda p: abs(complex(*p))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.