E731 не присваивайте лямбда-выражение, используйте def

193

Я получаю это предупреждение pep8 всякий раз, когда я использую лямбда-выражения. Лямбда-выражения не рекомендуются? Если нет, то почему?

Кечит Гоял
источник
4
Для ясности, вопрос относится к сообщению для автоматической регистрации flake8( flake8.pycqa.org )
rakslice

Ответы:

232

Рекомендация в PEP-8, с которой вы сталкиваетесь:

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

Да:

def f(x): return 2*x 

Нет:

f = lambda x: 2*x 

Первая форма означает, что имя результирующего функционального объекта определенно 'f' вместо общего '<lambda>'. Это более полезно для трассировок и строковых представлений в целом. Использование оператора присваивания исключает единственное преимущество, которое лямбда-выражение может предложить по сравнению с явным оператором def (то есть то, что оно может быть встроено в большее выражение)

Присвоение лямбды именам в основном просто дублирует функциональность def - и вообще, лучше всего сделать что-то одним способом, чтобы избежать путаницы и повысить ясность.

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

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

В общем, главный аргумент против этого заключается в том, что defоператоры приведут к большему количеству строк кода. Мой главный ответ на это будет: да, и это хорошо. Если вы не занимаетесь кодовым гольфом, то минимизировать количество линий - это не то, что вам следует делать: переходите к ясному сокращению.

Гарет Латти
источник
5
Я не вижу, как это хуже. Трассировка по-прежнему будет содержать номер строки и исходный файл. Один может сказать «f», тогда как другой говорит «лямбда». Может быть, лямбда-ошибку легче сканировать, потому что это не односимвольное имя функции или длинное имя с плохим именем?
g33kz0r
4
@ g33kz0r Ну, конечно, если вы предполагаете, что остальная часть вашего кода будет плохого качества, следующие соглашения не принесут вам большой пользы. В общем, нет, это не конец света, но это все еще плохая идея.
Гарет Латти
40
Этот ответ не очень полезен, потому что при запуске предложенного подхода с использованием defпроверки PEP8 вы получаете E704 multiple statements on one line (def), а если вы разбиваете его на две строки, вы получаете E301 expected 1 blank line, found 0: - /
Adam Spiers
4
Я согласен, что это должно быть разделено. Мои соображения заключались в том, что а) он не разделен в приведенном выше коде ответа, вызывая E704, и б) если вы разбили его, вам нужна ужасная пустая строка над ним, чтобы избежать E301.
Адам Спайерс
3
Я использую лямбду, когда хочу подчеркнуть чистую функцию (без побочных эффектов), и иногда мне приходится использовать одну и ту же функцию в двух местах, то есть группировать и сортировать вместе. Поэтому я игнорирую это соглашение.
Ман
120

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

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

Это только для представления, я столкнулся с несколькими различными версиями этого.

Теперь, чтобы сохранить вещи СУХОЙ, я начинаю использовать эту обычную лямбду.

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

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

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

Теперь контролер жалуется, что функция должна быть ограничена одной пустой строкой до и после.

def f(x):
    return x + offset

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

Здесь у нас теперь есть 6 строк кода вместо исходных 2 строк без увеличения читабельности и увеличения числа пифонов. В этот момент программа проверки кода жалуется на то, что функция не имеет строк документации.

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

iankit
источник
13
a = [x + offset for x in simple_list], Не нужно использовать mapи lambdaздесь.
Георгий
9
@ Георгий Я считаю, что цель состояла в том, чтобы переместить x + offsetчасть в абстрактное место, которое можно обновить без изменения более чем одной строки кода. Как вы уже упоминали, со списочными пониманиями вам по-прежнему понадобятся две строки кода, которые содержат x + offsetих, которые теперь будут в списочных пониманиях. Чтобы вытащить их так, как хотел автор, вам понадобится defили lambda.
Джулиан
1
@Julian Помимо defи lambdaможно также использовать functools.partial : f = partial(operator.add, offset)а затем a = list(map(f, simple_list)).
Георгий
Как насчет def f(x): return x + offset(то есть, простая функция, определенная в одной строке)? По крайней мере с flake8 я не получаю жалоб на пустые строки.
DocOc
1
@Julian В некоторых случаях вы можете использовать вложенное понимание:a, b = [[x + offset for x lst] for lst in (simple_list, another_simple_list)]
wjandrea
24

Lattyware абсолютно прав: в основном PEP-8 хочет, чтобы вы избегали таких вещей, как

f = lambda x: 2 * x

и вместо этого использовать

def f(x):
    return 2 * x

Однако, как указано в недавнем отчете об ошибках (август 2014 г.), такие утверждения, как следующие, теперь соответствуют:

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

Поскольку моя программа проверки PEP-8 еще не реализовала это правильно, я на время отключил E731.

Эльмар Пайз
источник
8
Даже при использовании defпрограмма проверки PEP8 жалуется E301 expected 1 blank line, found 0, поэтому вам нужно добавить некрасивую пустую строку перед ней.
Адам Спирс
1

Я также столкнулся с ситуацией, в которой было даже невозможно использовать функцию def (ined).

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},
  }

В этом случае я действительно хотел сделать отображение, которое принадлежало классу. Некоторые объекты в отображении нуждались в той же функции. Было бы нелогично помещать именованную функцию вне класса. Я не нашел способ ссылаться на метод (статический метод, метод класса или обычный) изнутри тела класса. SomeClass еще не существует, когда код выполняется. Так что ссылаться на него из класса тоже невозможно.

простофиля
источник
Вы можете сослаться also_not_reachableв определении на карту какSomeClass.also_not_reachable
yaccz
1
Я не знаю, что вы пытаетесь сделать здесь. Каждое из ваших имен функций fдля меня так же доступно, как и в 2.7, и в 3.5
Эрик
Нет, все функции, кроме лямбда-функции, недоступны изнутри тела класса. Вы получите объект AttributeError: type «SomeClass» не имеет атрибута «...», если попытаетесь получить доступ к одной из этих функций в объекте some_mapping.
СимП
3
@simP все они прекрасно доступны. Те, кто имеет @staticmethodи @classmethodне нуждается в объекте, просто SomeClass.also_not_reachable(хотя им нужны отличительные имена). Если вам нужен доступ к ним из методов класса, просто используйтеself.also_not_reachable
ababak
@simP, может быть, тебе следует переименовать свои *not_reachableметоды в not_as_easily_reachable_from_class_definition_as_a_lambdaxD
Ромэн Винсент