Что предпочтительнее использовать: лямбда-функции или вложенные функции (def)?

104

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

Вот несколько тривиальных примеров, когда они функционально делают то же самое, если они были обнаружены в другой функции:

Лямбда-функция

>>> a = lambda x : 1 + x
>>> a(5)
6

Вложенная функция

>>> def b(x): return 1 + x

>>> b(5)
6

Есть ли преимущества в использовании одного перед другим? (Производительность? Читаемость? Ограничения? Последовательность? И т. Д.)

Это вообще имеет значение? Если этого не происходит, это нарушает принцип Питона:

Должен быть один - и желательно только один - очевидный способ сделать это .

Луч
источник

Ответы:

108

Если вам нужно присвоить lambdaимя, используйте defвместо него. defs - это просто синтаксический сахар для присваивания, поэтому результат тот же, и они намного более гибкие и удобочитаемые.

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

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

Встроенные функции map()и filter()необходимость функция объектов, но списочные и выражение генератора , как правило , более удобные для чтения , чем те функции , и может охватить все случаи использования, без необходимости лямбды.

В случаях, когда вам действительно нужен небольшой функциональный объект, вы должны использовать operatorфункции модуля, например, operator.addвместоlambda x, y: x + y

Если вам все еще нужно что-то lambdaне охваченное, вы можете подумать о написании def, просто для большей читабельности. Если функция более сложная, чем в operatorмодуле, defвероятно, лучше использовать a .

Итак, хорошие lambdaварианты использования в реальном мире очень редки.

носкло
источник
10
Я согласен с ответом на вопрос, когда использовать lambda, но я не согласен с тем, что это «очень редко», обычно для ключевых функций sortedи itertools.groupbyт. Д., Напримерsorted(['a1', 'b0'], key= lambda x: int(x[1]))
Chris_Rands 09
30

На практике у меня есть два отличия:

Первый касается того, что они делают и что возвращают:

  • def - это ключевое слово, которое ничего не возвращает и создает «имя» в локальном пространстве имен.

  • лямбда - это ключевое слово, которое возвращает объект функции и не создает «имя» в локальном пространстве имен.

Следовательно, если вам нужно вызвать функцию, которая принимает объект функции, единственный способ сделать это в одной строке кода Python - использовать лямбда. У def нет эквивалента.

В некоторых фреймворках это действительно довольно часто; например, я часто использую Twisted , поэтому делаю что-то вроде

d.addCallback(lambda result: setattr(self, _someVariable, result))

довольно распространен и более краток с лямбдами.

Второе отличие заключается в том, что разрешено делать фактической функции.

  • Функция, определенная с помощью def, может содержать любой код Python
  • Функция, определенная с помощью lambda, должна оценивать выражение и, следовательно, не может содержать такие операторы, как print, import, raise, ...

Например,

def p(x): print x

работает как положено, а

lambda x: print x

это SyntaxError.

Конечно, есть обходные пути - заменить printна sys.stdout.writeили importна __import__. Но обычно в этом случае лучше использовать функцию.

Томас Вандер Стихеле
источник
23

В этом интервью Гвидо ван Россум говорит, что ему жаль, что он не пустил лямбду в Python:

« В. Какая функция Python вам меньше всего нравится?

Иногда я слишком быстро принимал участие, а позже понимал, что это была ошибка. Одним из примеров могут быть некоторые функции функционального программирования, такие как лямбда-функции. Лямбда - это ключевое слово, позволяющее создать небольшую анонимную функцию; встроенные функции, такие как map, filter и reduce, запускают функцию над типом последовательности, например списком.

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

ИМХО, ямбды иногда могут быть удобными, но обычно они удобны за счет удобочитаемости. Вы можете сказать мне, что это делает:

str(reduce(lambda x,y:x+y,map(lambda x:x**x,range(1,1001))))[-10:]

Я написал это, и мне потребовалась минута, чтобы понять это. Это из Project Euler - не скажу, какая проблема, потому что ненавижу спойлеры, но она выполняется за 0,124 секунды :)

Крис Лоулор
источник
20
Обратите внимание, что интервью довольно старое, и Python уже давно добавил вложенные области видимости, что делает аргумент, который он приводит против лямбды, больше не актуален. Я уверен, что он все еще сожалеет о лямбде, но недостаточно, чтобы удалить его в Python 3.0.
Томас Воутерс,
10
На самом деле ваш пример должен быть аргументом против однострочных, а не лямбда-выражений. Кроме того, вам следовало использовать встроенную функцию суммы вместо сокращения с помощью лямбда: str (sum (map (lambda x: x ** x, range (1001)))) [: - 10]
Триптих
2
@ThomasWouters: Я понимаю, что отказ от удаления в версии lambda3.0 был близок, и что Гвидо не боролся за его сохранение.
Итан Фурман
11

Для n = 1000 вот некоторое время для вызова функции по сравнению с лямбдой:

In [11]: def f(a, b):
             return a * b

In [12]: g = lambda x, y: x * y

In [13]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    f(a, b)
   ....:
100 loops, best of 3: 285 ms per loop

In [14]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    g(a, b)
   ....:
100 loops, best of 3: 298 ms per loop

In [15]: %%timeit -n 100
for a in xrange(n):
  for b in xrange(n):
    (lambda x, y: x * y)(a, b)
   ....:
100 loops, best of 3: 462 ms per loop
Энди Хайден
источник
3
Интересно видеть, что лямбда и определенные версии примерно эквивалентны. Последний тест занял больше времени, потому что python, вероятно, нужно было выделять пространство каждый раз, когда он определял эту лямбда-функцию.
hlin117
Я предполагаю, что это имеет смысл, поскольку определение может ссылаться на локальные переменные (которые, возможно, изменились) ... хотя в случае, когда это не так, как здесь, cpython мог бы работать лучше.
Энди Хейден
Используйте dis.dis; Ваш (лямбда x, y: x * y) создает функцию в каждом цикле. Если вы создадите лямбда до цикла (он же f = lambda x, y: x * y), байт-код для вызова функции будет точно таким же, как g / f в вашем предыдущем примере, поэтому производительность лямбда такая же как функцию def. Так что lambda или def не повлияют, если вы используете их одинаково. Сделайте обратное, объявите функцию f () в цикле, затем вызовите ее ...
tito
@tito Я считаю, что это именно то, что демонстрируют 3 временных примера ...
Энди Хайден
@tito о, вы говорите, что определяете функцию в цикле, конечно, но я бы сказал, что это необычный шаблон. Не уверен, почему это потребовало отрицательного голоса по этому комментарию ...
Энди Хайден
7

Производительность:

Создание функции с lambdaэто немного быстрее , чем создавать его def. Разница связана с defсозданием записи имени в таблице locals. Полученная функция имеет такую ​​же скорость выполнения.


Читаемость:

Лямбда-функции несколько менее читабельны для большинства пользователей Python, но в некоторых случаях они гораздо более краткие. Рассмотрите возможность перехода от нефункциональной рутины к функциональной:

# Using non-functional version.

heading(math.sqrt(v.x * v.x + v.y * v.y), math.atan(v.y / v.x))

# Using lambda with functional version.

fheading(v, lambda v: math.sqrt(v.x * v.x + v.y * v.y), lambda v: math.atan(v.y / v.x))

# Using def with functional version.

def size(v):
    return math.sqrt(v.x * v.x + v.y * v.y)

def direction(v):
    return math.atan(v.y / v.x)

deal_with_headings(v, size, direction)

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


Ограничения:

  • lambda функции можно использовать только один раз, если они не присвоены имени переменной.
  • lambdaфункции, присвоенные именам переменных, не имеют преимущества перед defфункциями.
  • lambda функции могут быть трудными или невозможными для маринования.
  • def Имена функций должны быть тщательно выбраны, чтобы они были достаточно информативными и уникальными или, по крайней мере, иначе не использовались в области действия.

Последовательность:

Python в основном избегает соглашений о функциональном программировании в пользу процедурной и более простой объектной семантики. lambdaОператор находится в прямом противоречии с этим уклоном. Более того, как альтернатива уже распространенному def, lambdaфункция добавляет разнообразия в ваш синтаксис. Некоторые сочтут это менее последовательным.


Ранее существовавшие функции:

Как отмечали другие, многие варианты использования lambdaв полевых условиях могут быть заменены членами того же operatorили других модулей. Например:

do_something(x, y, lambda x, y: x + y)
do_something(x, y, operator.add)

Использование уже существующей функции во многих случаях может сделать код более читабельным.


Принцип Питона: «Должен быть один - а желательно только один - очевидный способ сделать это»

Это похоже на доктрину единственного источника истины . К сожалению, принцип «единственно очевидный способ сделать это» всегда был для Python скорее задумчивым стремлением, чем истинным руководящим принципом. Рассмотрим очень мощное понимание массивов в Python. Они функционально эквивалентно mapи filterфункций:

[e for e in some_array if some_condition(e)]
filter(some_array, some_condition)

lambdaи defтакие же.

Это вопрос мнения, но я бы сказал, что все на языке Python, предназначенное для общего использования, что, очевидно, ничего не ломает, является достаточно «питоническим».

Пи Мариллион
источник
7

Предпочтительнее: лямбда-функции или вложенные функции ( def)?

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

Есть несколько недостатков:

  • без имени (просто '<lambda>')
  • нет документации
  • без аннотаций
  • никаких сложных заявлений

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

Первый момент - это объекты одного типа

Лямбда приводит к тому же типу объекта, что и обычная функция.

>>> l = lambda: 0
>>> type(l)
<class 'function'>
>>> def foo(): return 0
... 
>>> type(foo)
<class 'function'>
>>> type(foo) is type(l)
True

Поскольку лямбды - это функции, они являются объектами первого класса.

И лямбды, и функции:

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

Но в лямбдах по умолчанию отсутствуют некоторые вещи, которые функции получают через полный синтаксис определения функции.

Ламба - __name__это'<lambda>'

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

>>> l.__name__
'<lambda>'
>>> foo.__name__
'foo'

Таким образом, лямбды нельзя искать программно в их пространстве имен.

Это ограничивает некоторые вещи. Например, fooможно найти сериализованный код, но lнельзя:

>>> import pickle
>>> pickle.loads(pickle.dumps(l))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fbbc0464e18>: 
attribute lookup <lambda> on __main__ failed

Мы можем искать fooпросто - потому что он знает свое собственное имя:

>>> pickle.loads(pickle.dumps(foo))
<function foo at 0x7fbbbee79268>

У лямбда-выражений нет аннотаций и нет строки документации

В основном лямбды не документированы. Давайте перепишем, fooчтобы лучше документировать:

def foo() -> int:
    """a nullary function, returns 0 every time"""
    return 0

Теперь у foo есть документация:

>>> foo.__annotations__
{'return': <class 'int'>}
>>> help(foo)
Help on function foo in module __main__:

foo() -> int
    a nullary function, returns 0 every time

Принимая во внимание, что у нас нет такого же механизма для передачи той же информации лямбдам:

>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda (...)

Но мы можем их взломать:

>>> l.__doc__ = 'nullary -> 0'
>>> l.__annotations__ = {'return': int}
>>> help(l)
Help on function <lambda> in module __main__:

<lambda> lambda ) -> in
    nullary -> 0

Но, вероятно, есть какая-то ошибка, которая портит вывод справки.

Лямбды могут возвращать только выражение

Лямбды не могут возвращать сложные инструкции, только выражения.

>>> lambda: if True: 0
  File "<stdin>", line 1
    lambda: if True: 0
             ^
SyntaxError: invalid syntax

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

Мы используем Python для ясности и удобства обслуживания. Чрезмерное использование лямбд может сработать.

Только вверх для лямбды: может быть создано в одном выражении

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

Создание функции внутри вызова функции позволяет избежать (недорогого) поиска имени по сравнению с поиском в другом месте.

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

Для очень простого выражения я мог бы выбрать лямбду.

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

import timeit

def return_nullary_lambda(return_value=0):
    return lambda: return_value

def return_nullary_function(return_value=0):
    def nullary_fn():
        return return_value
    return nullary_fn

И сейчас:

>>> min(timeit.repeat(lambda: return_nullary_lambda(1)))
0.24312214995734394
>>> min(timeit.repeat(lambda: return_nullary_function(1)))
0.24894469301216304

Я считаю, что небольшая разница во времени, указанная выше, может быть связана с поиском имени в return_nullary_function- обратите внимание, что это очень незначительно.

Вывод

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

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

Мы знаем, что должны давать нашим объектам хорошие имена. Как мы можем это сделать, если у объекта нет имени?

По всем этим причинам я обычно предпочитаю создавать функции с помощью, defа не с lambda.

Аарон Холл
источник
6

Я согласен с советом nosklo: если вам нужно дать функции имя, используйте def. Я резервирую lambdaфункции для случаев, когда я просто передаю короткий фрагмент кода другой функции, например:

a = [ (1,2), (3,4), (5,6) ]
b = map( lambda x: x[0]+x[1], a )
Дэн Ленски
источник
3
В большинстве комбинаций map / lambda вы можете заменить его пониманием списка или более подходящей функцией. Например, «map (sum, a)» или «[x [0] + x [1] for x in a]»
Джон Милликин,
Да, это правда. Хотя иногда я предпочитаю map (). По большей части это был просто надуманный пример использования встроенной функции.
Дэн Ленски,
точно ... Большинство примеров надуманы, потому что их использовать неестественно, и в большинстве случаев есть более практичные способы.
носкло
5

Соглашаясь с другими ответами, иногда это более читабельно. Вот пример, который lambdaмне пригодится, в случае использования, с которым я постоянно сталкиваюсь с N-мерным defaultdict.
Вот пример:

from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
d['Foo']['Bar'].append(something)

Я считаю это более читаемым, чем создание defвторого измерения. Это еще более важно для более высоких измерений.

Джонатан
источник
from functools import partial; defaultdict(partial(defaultdict, list)). Назначьте партиал имени, если вы хотите использовать его более одного раза. Но если вы продолжаете встречать эту конструкцию, это означает, что вы не СУХИЙ. Разложите его на служебную библиотеку. Вы можете использовать эту конструкцию для создания произвольного n-мерного defaultdict с помощью других функций (или цикла или рекурсии).
DylanYoung
3

Основное использование лямбда всегда было для простых функций обратного вызова, а также для сопоставления, уменьшения и фильтрации, которые требуют функции в качестве аргумента. Со списком понимание становится нормой, а добавленное разрешено, если, как в:

x = [f for f in range(1, 40) if f % 2]

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

пнг
источник
3

Важным ограничением лямбда-выражений является то, что они не могут содержать ничего, кроме выражения. Для лямбда-выражения практически невозможно произвести что-либо, кроме тривиальных побочных эффектов, поскольку оно не может иметь такого богатого тела, какdef функция 'ed.

При этом Lua повлиял на мой стиль программирования в сторону широкого использования анонимных функций, и я засорял ими свой код. Вдобавок ко всему, я склонен думать о map / reduce как о абстрактных операторах способами, которые я не рассматриваю в понимании списков или генераторах, почти как если бы я явно откладывал решение о реализации, используя эти операторы.

Редактировать: это довольно старый вопрос, и мое мнение по этому поводу несколько изменилось.

Во-первых, я категорически против присвоения lambdaвыражения переменной; поскольку у python есть специальный синтаксис только для этого (подсказка def). В дополнение к этому, многие варианты использования лямбда, даже если они не получают имени, имеют предопределенные (и более эффективные) реализации. Например, рассматриваемый пример можно сократить до просто (1).__add__, без необходимости заключать его в символы lambdaили def. Многие другие распространенные применения могут быть удовлетворены некоторой комбинацией модулей operator, itertoolsи functools.

SingleNegationElimination
источник
1
(1).__add__- прямой вызов dunder-методов почти никогда не должен происходить. Тысяча lambdaс за каждый прямой вызов.
Итан Фурман
1
@EthanFurman: Ну, по моему опыту, призывы природы (1).__add__несколько необычны, но я бы даже близко не подошел к «следует». без сомнения, я считаю, что первое гораздо более читабельно lambda x: 1 + x. Если бы у нас было что-то более похожее на нотацию срезов haskells, (1+)это было бы здорово, но мы должны обойтись тем, что семантически является именно этой вещью, именем метода dunder.
SingleNegationElimination
2
  • Время вычисления.
  • Функция без названия.
  • Для достижения одной функции и многих используют функциональность.

Рассмотрим простой пример,

# CREATE ONE FUNCTION AND USE IT TO PERFORM MANY OPERATIONS ON SAME TYPE OF DATA STRUCTURE.
def variousUse(a,b=lambda x:x[0]):
    return [b(i) for i in a]

dummyList = [(0,1,2,3),(4,5,6,7),(78,45,23,43)]
variousUse(dummyList)                           # extract first element
variousUse(dummyList,lambda x:[x[0],x[2],x[3]]) # extract specific indexed element
variousUse(dummyList,lambda x:x[0]+x[2])        # add specific elements
variousUse(dummyList,lambda x:x[0]*x[2])        # multiply specific elements
Бхаргав Патель
источник
1

Если вы просто собираетесь присвоить лямбду переменной в локальной области видимости, вы также можете использовать def, потому что она более читабельна и может быть легко расширена в будущем:

fun = lambda a, b: a ** b # a pointless use of lambda
map(fun, someList)

или

def fun(a, b): return a ** b # more readable
map(fun, someList)
слишком много php
источник
Оба from operator import pow;map(pow, someList)и (a**b for a,b in someList)даже более читабельны.
InQβ
1

Я нашел одно использование лямбда-выражений ... в отладочных сообщениях.

Поскольку лямбды могут быть вычислены лениво, у вас может быть такой код:

log.debug(lambda: "this is my message: %r" % (some_data,))

вместо возможно дорогих:

log.debug("this is my message: %r" % (some_data,))

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

Конечно, чтобы он работал, как описано, используемый модуль регистрации должен поддерживать лямбда-выражения как «ленивые параметры» (как это делает мой модуль регистрации).

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

Например, этот пользовательский тернарный оператор:

def mif(condition, when_true, when_false):
    if condition:
         return when_true()
    else:
         return when_false()

mif(a < b, lambda: a + a, lambda: b + b)

вместо того:

def mif(condition, when_true, when_false):
    if condition:
         return when_true
    else:
         return when_false

mif(a < b, a + a, b + b)

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

Конечно, вы можете просто использовать функции вместо лямбда-выражений, но для коротких выражений лямбда-выражения (c) более компактны.

Glushiator
источник
1
NB loggingуже имеет ленивое форматирование: log.debug("this is my message: %r", some_data)будет форматироваться только тогда, когда / если сообщение запрошено.
j08lue
@ j08lue лямбда-метод пропускает оценку всего в случае, если вывод отладки не создается, в случае, если вы показываете, что это some_dataможет быть дорогостоящее выражение или вызов функции / метода.
Glushiator
0

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

Например:

У вас есть функция с этой сигнатурой: myFunction (данные, функция обратного вызова).

Вы хотите передать функцию, которая добавляет 2 элемента.

Использование лямбда:

myFunction(data, (lambda x, y : x + y))

Питонический путь:

import operator
myFunction(data, operator.add)

Или, конечно, это простой пример, но есть много вещей, которые предоставляет операторный модуль, включая установщики / получатели элементов для list и dict. Очень круто.

e-satis
источник
-1

Основное отличие состоит в том, что вы не можете использовать defвстроенные функции, что, на мой взгляд, является наиболее удобным вариантом использования lambdaфункции. Например, при сортировке списка объектов:

my_list.sort(key=lambda o: o.x)

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

Али Расим Кокал
источник
-2

лямбда полезна для создания новых функций:

>>> def somefunc(x): return lambda y: x+y
>>> f = somefunc(10)
>>> f(2)
12
>>> f(4)
14
scrpy
источник