Що краще використовувати: лямбда-функції або вкладені функції ('def')?


101

Я в основному використовую лямбда-функції, але іноді використовую вкладені функції, які, здається, забезпечують таку саму поведінку.

Ось кілька тривіальних прикладів, коли вони функціонально роблять те саме, якщо будь-який був знайдений в іншій функції:

Функція лямбда

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

Вкладена функція

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

>>> b(5)
6

Чи є переваги використання одного над іншим? (Продуктивність? Читання? Обмеження? Послідовність? Тощо)

Це навіть має значення? Якщо це не так, це порушує пітонічний принцип:

Повинен бути один - і бажано лише один - очевидний спосіб зробити це .

Відповіді:


105

Якщо вам потрібно призначити lambdaім'я, використовуйте defнатомість. defs - це лише синтаксичний цукор для завдання, тому результат той самий, і вони набагато більш гнучкі та читаються.

lambdas може бути використаний для використання один раз, викидайте функції, які не мають імені.

Однак такий випадок використання зустрічається дуже рідко. Вам рідко потрібно обходити об'єкти без назви функцій.

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

У випадках, коли вам справді потрібен невеликий функціональний об'єкт, слід використовувати функції operatorмодуля, як-от operator.addзамістьlambda x, y: x + y

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

Отже, lambdaвипадки корисного використання в реальному світі дуже рідкісні.


9
Я згоден з відповіддю на коли використовувати lambda, але я НЕ згоден , що це «дуже рідко», він є загальним для основних функцій sortedабо itertools.groupbyт.п., наприкладsorted(['a1', 'b0'], key= lambda x: int(x[1]))
Chris_Rands

30

Практично кажучи, для мене є дві відмінності:

Перший - про те, що вони роблять і що повертають:

  • def - це ключове слово, яке нічого не повертає і створює "ім'я" в локальному просторі імен.

  • лямбда - це ключове слово, яке повертає об’єкт функції і не створює "ім'я" в локальному просторі імен.

Отже, якщо вам потрібно викликати функцію, яка приймає об’єкт функції, єдиний спосіб зробити це в одному рядку коду python - це лямбда. Там немає еквівалента def.

У деяких рамках це насправді досить часто; наприклад, я багато використовую Twisted , і так роблю щось подібне

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

є досить поширеним і більш лаконічним з лямбдами.

Друга відмінність полягає в тому, що дозволено виконувати фактичну функцію.

  • Функція, визначена 'def', може містити будь-який код python
  • Функція, визначена за допомогою "лямбда", повинна оцінювати до виразу, і, таким чином, не може містити заяви, такі як друк, імпорт, підвищення, ...

Наприклад,

def p(x): print x

працює, як очікувалося, поки

lambda x: print x

є SyntaxError.

Звичайно, є обхідні шляхи - замінити printна sys.stdout.write, або importз __import__. Але зазвичай вам краще перейти з функцією в такому випадку.


22

У цьому інтерв'ю Гвідо ван Россум каже, що хоче, щоб він не впустив "лямбда" в Python:

" Питання: Якою особливістю Python ви найменше задоволені?

Іноді я занадто швидко сприймав внески, а пізніше зрозумів, що це помилка. Одним із прикладів можуть бути деякі функції функціонального програмування, наприклад, лямбда-функції. Lambda - це ключове слово, яке дозволяє створювати невелику анонімну функцію; вбудовані функції, такі як карта, фільтр та зменшення запуску функції над типом послідовностей, таким як список.

На практиці це не вийшло так добре. Python має лише дві сфери: локальну та глобальну. Це робить написання лямбда-функцій болючим, оскільки вам часто потрібно отримати доступ до змінних у тій області, де була визначена лямбда, але ви не можете через дві області. Існує такий шлях, але це щось неприємне. У Python часто здається набагато простіше просто використовувати цикл, а не возитися з лямбда-функціями. карта та друзі працюють добре лише тоді, коли вже є вбудована функція, яка робить те, що ви хочете.

IMHO, Iambdas може бути зручним часом, але зазвичай є зручним за рахунок читальності. Чи можете ви сказати мені, що це робить:

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 (лямбда x: x ** x, діапазон (1001))))) [: - 10]
Триптих

2
@ThomasWouters: Я розумію, що lambdaне видаляти в 3.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
Цікаво побачити, що лямбда та визначені версії приблизно рівнозначні. Останній тест зайняв більше часу, оскільки пітону, ймовірно, потрібно було виділяти простір щоразу, коли він визначав цю лямбда-функцію.
hlin117

Я думаю, це має сенс, оскільки визначення може посилатися на локальні змінні (які, можливо, змінилися) ... хоча у випадку, коли це не так, як тут, cpython міг би зробити кращу роботу.
Енді Хейден

Використовуйте dis.dis; Ваш (лямбда х, у: х * у) створює функцію кожного циклу. Якщо ви створюєте лямбда перед циклом (aka f = lambda x, y: x * y), байт-код для виклику функції буде точно таким же, як g / f у вашому попередньому прикладі, тому продуктивність лямбда однакова як функція def. Так що лямбда або деф не впливають, якщо ви використовуєте те саме. Зробіть обернене, оголосіть функцію f () в циклі, а потім назвіть її ...
tito

@tito Я вважаю, що саме це демонструють три примірні приклади ...
Енді Хайден,

@tito о, ти кажеш, визначаючи функцію в циклі, звичайно, але я заперечую, що це незвична закономірність. Не впевнений, навіщо це потребує спростування цього коментаря ...
Енді Хейден

7

Продуктивність:

Створення функції з lambdaце трохи швидше , ніж створювати його def. Різниця пояснюється defстворенням імені в таблиці місцевих жителів. Отримана функція має однакову швидкість виконання.


Читання:

Функції лямбда дещо менш читабельні для більшості користувачів 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 (sum, a)" або "[x [0] + x [1] for x in a]"
Джон Міллікін

Так, це правда. Іноді я вважаю за краще карту (). Це був здебільшого лише надуманий приклад використання вбудованої функції.
Дан Ленськи

саме ... Більшість прикладів надумані, тому що це неприродно використовувати і в більшості випадків є практичні кращі способи.
nosklo

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-мірного рішення за замовчуванням за допомогою інших функцій (або циклу чи рекурсії).
DylanYoung

3

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

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

важко уявити реальний випадок використання лямбда у щоденному вживанні. Як результат, я б сказав, уникайте лямбда та створюйте вкладені функції.


3

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

Попри це, Луа вплинув на мій стиль програмування до широкого використання анонімних функцій, і я засмітив їх код. На додаток до цього я схильний думати про карту / зменшення як абстрактних операторів таким чином, щоб я не розглядав розуміння списку чи генератори, майже як якщо б я відкладав рішення про реалізацію явно за допомогою цих операторів.

Редагувати: Це досить старе питання, і мої думки з цього приводу дещо змінилися.

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


1
(1).__add__- закликати безпосередньо до методів, майже ніколи не має відбуватися. Тисяча lambdaс за кожен прямий дзвінок.
Етан Фурман

1
@EthanFurman: Ну, на мій досвід, дзвінки природи (1).__add__є дещо рідкісними, але я б не ходив ніде близько "слід". без сумніву, я вважаю, що колишнє значно читабельніше lambda x: 1 + x. Якби у нас було щось більш схоже на позначення фрагментів haskells, (1+)це було б чудово, але ми маємо робити те, що семантично є саме цією річчю, ім'я методу Дандера.
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)

І те, 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)

з лямбдами буде оцінено лише вираз, вибраний умовою, без лямбда обидва будуть оцінені.

Звичайно, ви можете просто використовувати функції замість лямбда, але для коротких виразів лямбда є (с) меншою.


1
NB loggingвже має ліниве форматування: форматуватиме log.debug("this is my message: %r", some_data)лише тоді, коли / якщо повідомлення буде запитано.
j08lue

@ j08lue метод лямбда пропускає оцінку всього, якщо вихід налагодження не виробляється, у випадку, якщо ви показуєте, це some_dataможе бути дорогим виразом або викликом функції / методу.
Glushiator

0

Я згоден з nosklo. До речі, навіть при використанні одного разу викиньте , більшу частину часу ви просто хочете використовувати щось з модуля оператора.

EG:

У вас є функція з цим підписом: myFunction (дані, функція зворотного дзвінка).

Ви хочете передати функцію, яка додає 2 елементи.

Використання лямбда:

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

Пітонічний спосіб:

import operator
myFunction(data, operator.add)

Або, звичайно, це простий приклад, але є багато речей, які надає модуль оператора, включаючи елементи, що задають / отримують елементи, для переліку та диктування. Дійсно здорово.


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