E731 nie przypisuj wyrażenia lambda, użyj def

193

Otrzymuję to ostrzeżenie pep8 za każdym razem, gdy używam wyrażeń lambda. Czy wyrażenia lambda nie są zalecane? Jeśli nie to dlaczego?

Kechit Goyal
źródło
4
Dla jasności pytanie dotyczy komunikatu do automatycznej odprawy flake8( flake8.pycqa.org )
rakslice

Odpowiedzi:

231

Zalecenia w PEP-8, na które się napotykasz, to:

Zawsze używaj instrukcji def zamiast instrukcji przypisania, która wiąże wyrażenie lambda bezpośrednio z nazwą.

Tak:

def f(x): return 2*x 

Nie:

f = lambda x: 2*x 

Pierwsza forma oznacza, że ​​nazwą wynikowego obiektu funkcji jest konkretnie „f” zamiast ogólnego „<lambda>”. Jest to bardziej przydatne w przypadku śledzenia wstecznego i reprezentacji ciągów w ogóle. Zastosowanie instrukcji przypisania eliminuje jedyną korzyść, jaką wyrażenie lambda może zaoferować w stosunku do jawnej instrukcji def (tzn. Że można ją osadzić w większym wyrażeniu)

Przypisywanie lambdas do nazw w zasadzie powiela funkcjonalność def- i ogólnie najlepiej zrobić coś w jeden sposób, aby uniknąć nieporozumień i zwiększyć przejrzystość.

Uzasadnionym przypadkiem użycia dla lambda jest miejsce, w którym chcesz użyć funkcji bez przypisania jej, np .:

sorted(players, key=lambda player: player.rank)

Zasadniczo głównym argumentem przeciwko temu jest to, że definstrukcje spowodują powstanie większej liczby wierszy kodu. Moja główna odpowiedź na to brzmi: tak, i jest w porządku. O ile nie grasz w golfa, minimalizowanie liczby linii nie jest czymś, co powinieneś robić: idź na skróty.

Gareth Latty
źródło
5
Nie rozumiem, jak jest gorzej. Funkcja śledzenia nadal będzie zawierać błędny numer linii i plik źródłowy. Jeden może powiedzieć „f”, a drugi „lambda”. Może błąd lambda jest łatwiejszy do skanowania, ponieważ nie jest to nazwa funkcji jednoznakowej lub źle nazwana długa nazwa?
g33kz0r
4
@ g33kz0r Cóż, oczywiście, jeśli zakładasz, że reszta kodu będzie niskiej jakości, przestrzeganie konwencji nie przyniesie wiele korzyści. Ogólnie rzecz biorąc, nie, to nie koniec świata, ale wciąż jest to zły pomysł.
Gareth Latty
39
Ta odpowiedź nie jest zbyt pomocna, ponieważ po uruchomieniu sugerowanego podejścia polegającego na użyciu defsprawdzania PEP8 otrzymujesz E704 multiple statements on one line (def), a jeśli podzielisz ją na dwie linie, otrzymasz E301 expected 1 blank line, found 0: - /
Adam Spiers
4
Zgadzam się, że powinien zostać podzielony. Chodzi mi o to, że a) nie jest podzielony w powyższym kodzie odpowiedzi, co powoduje E704, i b) jeśli go podzielisz, potrzebujesz brzydkiej pustej linii powyżej, aby uniknąć E301.
Adam Spiers,
3
Używam lambdas, gdy chcę podkreślić czystą funkcję (bez skutków ubocznych), a czasami muszę używać tej samej funkcji w dwóch miejscach, tj. Grupować i sortować razem. Dlatego ignoruję tę konwencję.
manu
119

Oto historia, miałem prostą funkcję lambda, z której korzystałem dwa razy.

a = map(lambda x : x + offset, simple_list)
b = map(lambda x : x + offset, another_simple_list)

To tylko dla reprezentacji, spotkałem się z kilkoma różnymi wersjami tego.

Teraz, aby utrzymać stan NA SUCHO, zaczynam ponownie używać tej wspólnej lambda.

f = lambda x : x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

W tym momencie mój kontroler jakości kodu skarży się, że lambda jest nazwaną funkcją, więc przekształcam ją w funkcję.

def f(x):
    return x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

Teraz moduł sprawdzający skarży się, że funkcja musi być ograniczona jedną pustą linią przed i po.

def f(x):
    return x + offset

a = map(f, simple_list)
b = map(f, another_simple_list)

Tutaj mamy teraz 6 wierszy kodu zamiast oryginalnych 2 wierszy bez zwiększania czytelności i bez zwiększania pythoniczności. W tym momencie narzędzie do sprawdzania kodu skarży się, że funkcja nie ma dokumentów.

Moim zdaniem tej zasady lepiej unikać i łamać, gdy ma to sens, skorzystaj z własnego osądu.

iankit
źródło
13
a = [x + offset for x in simple_list]. Nie musisz używać mapi lambdatutaj.
Georgy,
8
@Georgy Myślę, że chodziło o przeniesienie x + offsetczęści do abstrakcyjnej lokalizacji, którą można zaktualizować bez zmiany więcej niż jednego wiersza kodu. Z listami, które już wspomniałeś, nadal potrzebujesz dwóch linii kodu, które zawierałyby x + offsetje teraz w listach. Aby wyciągnąć je tak, jak chciał autor, potrzebujesz deflub lambda.
Julian
1
@Julian Oprócz defi lambdamożna również użyć funools.partial : f = partial(operator.add, offset)a następnie a = list(map(f, simple_list)).
Georgy,
Co z def f(x): return x + offset(tj. Prostą funkcją zdefiniowaną w jednym wierszu)? Przynajmniej z flake8 nie otrzymuję skarg na puste linie.
DocOc,
1
@Julian W niektórych przypadkach możesz użyć zagnieżdżonego rozumienia:a, b = [[x + offset for x lst] for lst in (simple_list, another_simple_list)]
wjandrea
24

Lattyware ma absolutną rację: Zasadniczo PEP-8 chce, abyś unikał takich rzeczy

f = lambda x: 2 * x

i zamiast tego użyj

def f(x):
    return 2 * x

Jednak, jak opisano w ostatnim raporcie o błędach (sierpień 2014 r.), Następujące oświadczenia są teraz zgodne:

a.f = lambda x: 2 * x
a["f"] = lambda x: 2 * x

Ponieważ mój moduł sprawdzania PEP-8 jeszcze tego nie implementuje, na razie wyłączyłem E731.

Elmar Peise
źródło
8
Nawet podczas używania defmoduł sprawdzania PEP8 narzeka E301 expected 1 blank line, found 0, więc musisz dodać brzydką pustą linię przed nim.
Adam Spiers,
1

Zetknąłem się również z sytuacją, w której korzystanie z funkcji def (ined) było niemożliwe.

class SomeClass(object):
  # pep-8 does not allow this
  f = lambda x: x + 1  # NOQA

  def not_reachable(self, x):
    return x + 1

  @staticmethod
  def also_not_reachable(x):
    return x + 1

  @classmethod
  def also_not_reachable(cls, x):
    return x + 1

  some_mapping = {
      'object1': {'name': "Object 1", 'func': f},
      'object2': {'name': "Object 2", 'func': some_other_func},
  }

W tym przypadku naprawdę chciałem stworzyć mapowanie należące do klasy. Niektóre obiekty w mapowaniu wymagały tej samej funkcji. Nielogiczne byłoby umieszczanie nazwanej funkcji poza klasą. Nie znalazłem sposobu na odwołanie się do metody (metoda statyczna, metoda klasowa lub normalna) z wnętrza klasy. SomeClass jeszcze nie istnieje po uruchomieniu kodu. Odwołanie się do tego z klasy też nie jest możliwe.

SIMP
źródło
Można zapoznać się also_not_reachablew definicji mapowania jakoSomeClass.also_not_reachable
yaccz
1
Nie wiem, o co tu chodzi. Każda nazwa twojej funkcji jest fdla mnie tak dostępna, jak w wersjach 2.7 i 3.5
Eric
Nie, wszystkie funkcje, z wyjątkiem funkcji lambda, nie są osiągalne z wnętrza klasy. Otrzymasz AttributeError: typ obiektu „SomeClass” nie ma atrybutu „...”, jeśli spróbujesz uzyskać dostęp do jednej z tych funkcji w obiekcie some_mapping.
simP
3
@simP wszystkie są doskonale dostępne. Te z @staticmethodi @classmethodnie potrzebują obiektu, tylko SomeClass.also_not_reachable(chociaż potrzebują charakterystycznych nazw). Jeśli chcesz uzyskać do nich dostęp metodami klasowymi, po prostu użyjself.also_not_reachable
ababak
@simP może powinieneś zmienić nazwę swojego *not_reachable metod na not_as_easily_reachable_from_class_definition_as_a_lambdaxD
Romain Vincent