Как использовать Python-лямбда-функции

Как использовать Python-лямбда-функции

В Python и других языках, таких как Java, C # и даже C ++, к их синтаксису добавлены лямбда-функции, в то время как языки, такие как LISP или семейство языков ML, Haskell, OCaml и F #, используют лямбда-выражения в качестве основной концепции.

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

К концу этой статьи вы будете знать:

  • Как Python лямбды стали

  • Как лямбды сравниваются с обычными объектами функций

  • Как написать лямбда-функции

  • Какие функции в стандартной библиотеке Python используют лямбда-выражения

  • Когда использовать или избегать Python лямбда-функций

Notes: вы увидите несколько примеров кода с использованиемlambda, которые явно игнорируют лучшие практики стиля Python. Это предназначено только для иллюстрации концепций лямбда-исчисления или для освещения возможностей Pythonlambda.

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

Это руководство предназначено в основном для опытных программистов на Python, но оно доступно для любых любопытных людей, интересующихся программированием и лямбда-исчислением.

Все примеры, включенные в это руководство, были протестированы с Python 3.7.

__ Take the Quiz: Проверьте свои знания с помощью нашей интерактивной викторины «Лямбда-функции Python». По окончании вы получите оценку, чтобы вы могли отслеживать прогресс в обучении с течением времени:

Free Bonus:Click here to get access to a chapter from Python Tricks: The Book, который демонстрирует вам лучшие практики Python на простых примерах, которые вы можете мгновенно применить для написания более красивого кода Pythonic.

Лямбда-исчисление

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

история

Alonzo Church формализовалlambda calculus, язык, основанный на чистой абстракции, в 1930-х годах. Лямбда-функции также называют лямбда-абстракциями, прямой ссылкой на абстракционную модель первоначального творения Алонзо Черч.

Лямбда-исчисление может закодировать любые вычисления. ЭтоTuring complete, но в отличие от концепции aTuring machine, он чистый и не сохраняет никакого состояния.

Функциональные языки берут свое начало в математической логике и лямбда-исчислении, в то время как императивные языки программирования охватывают основанную на состоянии модель вычислений, изобретенную Аланом Тьюрингом. Две модели вычислений, лямбда-исчисление иTuring machines, могут быть преобразованы друг в друга. Эта эквивалентность известна какChurch-Turing hypothesis.

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

Напротив, машина Тьюринга привела к императивному программированию, которое можно найти в таких языках, какFortran,C илиPython.

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

Разделение в обоих семействах имеет некоторые нюансы, поскольку некоторые функциональные языки включают императивные функции, такие какOCaml, в то время как функциональные особенности проникли в императивное семейство языков, в частности, с введением лямбда-функций вJava, или Python.

Python по своей сути не является функциональным языком, но на раннем этапе он принял некоторые функциональные концепции. В январе 1994 г. к языку были добавленыmap(),filter(),reduce() и операторlambda.

Первый пример

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

identity function, функция, которая возвращает свой аргумент, выражается в стандартном определении функции Python с использованием ключевого словаdef следующим образом:

>>>

>>> def identity(x):
...     return x

identity() принимает аргументx и возвращает его при вызове.

Напротив, если вы используете лямбда-конструкцию Python, вы получите следующее:

>>>

>>> lambda x: x

В приведенном выше примере выражение состоит из:

  • Ключевое слово: lambda

  • Связанная переменная: x

  • Тело: x

Note: в контексте этой статьиbound variable является аргументом лямбда-функции.

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

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

>>>

>>> lambda x: x + 1

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

>>>

>>> (lambda x: x + 1)(2)
3

Reduction - это стратегия лямбда-исчисления для вычисления значения выражения. Он состоит в замене аргумента2 наx:

(lambda x: x + 1)(2) = lambda 2: 2 + 1
                     = 2 + 1
                     = 3

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

>>>

>>> add_one = lambda x: x + 1
>>> add_one(2)
3

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

def add_one(x):
    return x + 1

Все эти функции принимают один аргумент. Возможно, вы заметили, что в определении лямбды аргументы не имеют круглых скобок вокруг них. Функции с несколькими аргументами (функции, которые принимают более одного аргумента) выражаются в лямбдах Python путем перечисления аргументов и разделения их запятой (,), но без заключения в круглые скобки:

>>>

>>> full_name = lambda first, last: f'Full name: {first.title()} {last.title()}'
>>> full_name('guido', 'van rossum')
'Full name: Guido Van Rossum'

Лямбда-функция, назначеннаяfull_name, принимает два аргумента и возвращает строку, интерполирующую два параметраfirst иlast. Как и ожидалось, в определении лямбды перечислены аргументы без скобок, тогда как вызов функции выполняется точно так же, как и обычная функция Python, с круглыми скобками вокруг аргументов.

Анонимные функции

Следующие термины могут использоваться взаимозаменяемо в зависимости от типа и культуры языка программирования:

  • Анонимные функции

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

  • Лямбда-выражения

  • Лямбда-абстракции

  • Лямбда-форма

  • Функциональные литералы

В остальной части статьи после этого раздела вы в основном будете встречать терминlambda function.

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

>>>

>>> lambda x, y: x + y

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

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

>>>

>>> _(1, 2)
3

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

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

Note: в интерактивном интерпретаторе одиночное подчеркивание (_) привязано к последнему вычисленному выражению.

В приведенном выше примере_ указывает на лямбда-функцию. Дополнительные сведения об использовании этого специального символа в Python см. ВThe Meaning of Underscores in Python.

Другой шаблон, используемый в других языках, таких как JavaScript, - это немедленное выполнение лямбда-функции Python. Это известно какImmediately Invoked Function Expression (IIFE, произносится как «если нет»). Вот пример:

>>>

>>> (lambda x, y: x + y)(2, 3)
5

Приведенная выше лямбда-функция определяется и затем сразу вызывается с двумя аргументами (2 и3). Он возвращает значение5, которое является суммой аргументов.

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

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

Лямбда-функции часто используются сhigher-order functions, которые принимают одну или несколько функций в качестве аргументов или возвращают одну или несколько функций.

Лямбда-функция может быть функцией более высокого порядка, принимая функцию (нормальную или лямбда-функцию) в качестве аргумента, как в следующем надуманном примере:

>>>

>>> high_ord_func = lambda x, func: x + func(x)
>>> high_ord_func(2, lambda x: x * x)
6
>>> high_ord_func(2, lambda x: x + 3)
7

Python предоставляет функции высшего порядка в виде встроенных функций или в стандартной библиотеке. Примеры включаютmap(),filter(),functools.reduce(), а также ключевые функции, такие какsort(),sorted(),min() иmax(). . Вы будете использовать лямбда-функции вместе с функциями высшего порядка Python вAppropriate Uses of Lambda Expressions.

Python лямбда и регулярные функции

Эта цитата изPython Design and History FAQ, кажется, задает тон в отношении общих ожиданий относительно использования лямбда-функций в Python:

В отличие от лямбда-форм в других языках, где они добавляют функциональность, лямбда-выражения Python являются лишь сокращенной записью, если вы слишком ленивы, чтобы определить функцию. (Source)

Тем не менее, не позволяйте этому утверждению удерживать вас от использования Pythonlambda. На первый взгляд, вы можете согласиться с тем, что лямбда-функция - это функция, у которойsyntactic sugar сокращает код для определения или вызова функции. В следующих разделах освещены общие черты и тонкие различия между обычными функциями Python и лямбда-функциями.

функции

На этом этапе вы можете задаться вопросом, что принципиально отличает лямбда-функцию, связанную с переменной, от обычной функции с единственной линиейreturn: под поверхностью почти ничего. Давайте посмотрим, как Python видит функцию, созданную с помощью одного оператора return, по сравнению с функцией, построенной как выражение (lambda).

Модульdis предоставляет функции для анализа байт-кода Python, сгенерированного компилятором Python:

>>>

>>> import dis
>>> add = lambda x, y: x + y
>>> type(add)

>>> dis.dis(add)
  1           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> add
 at 0x7f30c6ce9ea0>

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

Теперь посмотрим на это с помощью обычного объекта функции:

>>>

>>> import dis
>>> def add(x, y): return x + y
>>> type(add)

>>> dis.dis(add)
  1           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> add

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

Проследить

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

>>>

>>> div_zero = lambda x: x / 0
>>> div_zero(2)
Traceback (most recent call last):
    File "", line 1, in 
    File "", line 1, in 
ZeroDivisionError: division by zero

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

Вот то же исключение, вызванное нормальной функцией:

>>>

>>> def div_zero(x): return x / 0
>>> div_zero(2)
Traceback (most recent call last):
    File "", line 1, in 
    File "", line 1, in div_zero
ZeroDivisionError: division by zero

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

Синтаксис

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

  • Он может содержать только выражения и не может включать операторы в свое тело.

  • Это написано как одна строка исполнения.

  • Он не поддерживает аннотации типов.

  • Он может быть немедленно вызван (IIFE).

Нет заявлений

Лямбда-функция не может содержать никаких утверждений. В лямбда-функции такие операторы, какreturn,pass,assert илиraise, вызовут исключениеSyntaxError. Вот пример добавленияassert в тело лямбды:

>>>

>>> (lambda x: assert x == 2)(2)
  File "", line 1
    (lambda x: assert x == 2)(2)
                    ^
SyntaxError: invalid syntax

Этот надуманный пример предназначен дляassert, что параметрx имеет значение2. Но интерпретатор идентифицируетSyntaxError при анализе кода, который включает операторassert в телеlambda.

Одно выражение

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

>>>

>>> (lambda x:
... (x % 2 and 'odd' or 'even'))(3)
'odd'

В приведенном выше примере возвращается строка'odd', если лямбда-аргумент нечетный, и'even', если аргумент четный. Он распространяется на две строки, поскольку содержится в скобках, но остается одним выражением.

Тип Аннотации

Если вы начали применять хинтинг типов, который теперь доступен в Python, у вас есть еще одна веская причина предпочесть нормальные функции лямбда-функциям Python. ПросмотритеPython Type Checking (Guide), чтобы узнать больше о подсказках типов Python и проверке типов. В лямбда-функции нет эквивалента для следующего:

def full_name(first: str, last: str) -> str:
    return f'{first.title()} {last.title()}'

Любая ошибка типа сfull_name() может быть обнаружена такими инструментами, какmypy илиpyre, тогда какSyntaxError с эквивалентной лямбда-функцией возникает во время выполнения:

>>>

>>> lambda first: str, last: str: first.title() + " " + last.title() -> str
  File "", line 1
    lambda first: str, last: str: first.title() + " " + last.title() -> str

SyntaxError: invalid syntax

Как и при попытке включить оператор в лямбда, добавление аннотации типа немедленно приводит кSyntaxError во время выполнения.

IIFE

Вы уже видели несколько примеровimmediately invoked function execution:

>>>

>>> (lambda x: x * x)(3)
9

За пределами интерпретатора Python эта функция, вероятно, не используется на практике. Это прямое следствие того, что лямбда-функция вызывается так, как она определена. Например, это позволяет вам передать определение лямбда-выражения Python в функцию более высокого порядка, напримерmap(),filter() илиfunctools.reduce(), или в ключевую функцию.

аргументы

Как и обычный функциональный объект, определенный с помощьюdef, лямбда-выражения Python поддерживают все различные способы передачи аргументов. Это включает:

  • Позиционные аргументы

  • Именованные аргументы (иногда называемые ключевыми аргументами)

  • Переменный список аргументов (часто обозначается какvarargs)

  • Переменный список аргументов ключевых слов

  • Аргументы только для ключевых слов

Следующие примеры иллюстрируют опции, доступные вам для передачи аргументов в лямбда-выражения:

>>>

>>> (lambda x, y, z: x + y + z)(1, 2, 3)
6
>>> (lambda x, y, z=3: x + y + z)(1, 2)
6
>>> (lambda x, y, z=3: x + y + z)(1, y=2)
6
>>> (lambda *args: sum(args))(1,2,3)
6
>>> (lambda **kwargs: sum(kwargs.values()))(one=1, two=2, three=3)
6
>>> (lambda x, *, y=0, z=0: x + y + z)(1, y=2, z=3)
6

Декораторы

В Pythondecorator - это реализация шаблона, который позволяет добавлять поведение к функции или классу. Обычно это выражается с помощью синтаксиса@decorator перед функцией. Вот надуманный пример:

def some_decorator(f):
    def wraps(*args):
        print(f"Calling function '{f.__name__}'")
        return f(args)
    return wraps

@some_decorator
def decorated_function(x):
    print(f"With argument '{x}'")

В приведенном выше примереsome_decorator() - это функция, которая добавляет поведение кdecorated_function(), так что вызовdecorated_function("Python") приводит к следующему результату:

Calling function 'decorated_function'
With argument 'Python'

decorated_function() печатает толькоWith argument 'Python', но декоратор добавляет дополнительное поведение, которое также печатаетCalling function 'decorated_function'.

Декоратор может быть применен к лямбде. Хотя украсить лямбда синтаксисом@decorator невозможно, декоратор - это просто функция, поэтому он может вызывать лямбда-функцию:

 1 # Defining a decorator
 2 def trace(f):
 3     def wrap(*args, **kwargs):
 4         print(f"[TRACE] func: {f.__name__}, args: {args}, kwargs: {kwargs}")
 5         return f(*args, **kwargs)
 6
 7     return wrap
 8
 9 # Applying decorator to a function
10 @trace
11 def add_two(x):
12     return x + 2
13
14 # Calling the decorated function
15 add_two(3)
16
17 # Applying decorator to a lambda
18 print((trace(lambda x: x ** 2))(3))

add_two(), украшенный@trace в строке 11, вызывается с аргументом3 в строке 15. Напротив, в строке 18 сразу задействуется лямбда-функция, которая встраивается в вызовtrace(), декоратора. Когда вы выполняете код выше, вы получаете следующее:

[TRACE] func: add_two, args: (3,), kwargs: {}
[TRACE] func: , args: (3,), kwargs: {}
9

Посмотрите, как, как вы уже видели, имя лямбда-функции отображается как<lambda>, тогда какadd_two четко идентифицируется как обычная функция.

Оформление лямбда-функции таким способом может быть полезно для целей отладки, возможно, для отладки поведения лямбда-функции, используемой в контексте функции более высокого порядка или ключевой функции. Давайте посмотрим на пример сmap():

list(map(trace(lambda x: x*2), range(3)))

Первый аргументmap() - это лямбда, которая умножает свой аргумент на2. Эта лямбда украшенаtrace(). При выполнении приведенный выше пример выводит следующее:

[TRACE] Calling  with args (0,) and kwargs {}
[TRACE] Calling  with args (1,) and kwargs {}
[TRACE] Calling  with args (2,) and kwargs {}
[0, 2, 4]

Результат[0, 2, 4] - это список, полученный умножением каждого элементаrange(3). А пока рассмотримrange(3) как эквивалент списка[0, 1, 2].

Более подробная информация оmap() будет представлена ​​вMap.

Лямбда также может быть декоратором, но это не рекомендуется. Если вам нужно это сделать, обратитесь кPEP 8, Programming Recommendations.

Чтобы узнать больше о декораторах Python, посетитеPrimer on Python Decorators.

закрытие

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

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

Вот замыкание, построенное с помощью обычной функции Python:

 1 def outer_func(x):
 2     y = 4
 3     def inner_func(z):
 4         print(f"x = {x}, y = {y}, z = {z}")
 5         return x + y + z
 6     return inner_func
 7
 8 for i in range(3):
 9     closure = outer_func(i)
10     print(f"closure({i+5}) = {closure(i+5)}")

outer_func() возвращаетinner_func(), вложенную функцию, которая вычисляет сумму трех аргументов:

  • x передается как аргументouter_func().

  • y - переменная, локальная дляouter_func().

  • z - это аргумент, переданныйinner_func().

Чтобы проверить поведениеouter_func() иinner_func(),outer_func() вызывается три раза в циклеfor, который выводит следующее:

x = 0, y = 4, z = 5
closure(5) = 9
x = 1, y = 4, z = 6
closure(6) = 11
x = 2, y = 4, z = 7
closure(7) = 13

В строке 9 кодаinner_func(), возвращаемый вызовомouter_func(), привязан к имениclosure. В строке 5inner_func() захватываетx иy, потому что он имеет доступ к своей среде встраивания, так что при вызове закрытия он может работать с двумя свободными переменнымиx иy.

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

 1 def outer_func(x):
 2     y = 4
 3     return lambda z: x + y + z
 4
 5 for i in range(3):
 6     closure = outer_func(i)
 7     print(f"closure({i+5}) = {closure(i+5)}")

Когда вы выполните код выше, вы получите следующий вывод:

closure(5) = 9
closure(6) = 11
closure(7) = 13

В строке 6outer_func() возвращает лямбду и присваивает ее переменнойclosure. В строке 3 тело лямбда-функции ссылается наx иy. Переменнаяy доступна во время определения, тогда какx определяется во время выполнения, когда вызываетсяouter_func().

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

Время оценки

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

Сначала протестируйте сценарий, используя обычную функцию:

>>>

 1 >>> def wrap(n):
 2 ...     def f():
 3 ...         print(n)
 4 ...     return f
 5 ...
 6 >>> numbers = 'one', 'two', 'three'
 7 >>> funcs = []
 8 >>> for n in numbers:
 9 ...     funcs.append(wrap(n))
10 ...
11 >>> for f in funcs:
12 ...     f()
13 ...
14 one
15 two
16 three

В нормальной функцииn оценивается во время определения, в строке 9, когда функция добавляется в список:funcs.append(wrap(n)).

Теперь, с реализацией той же логики с лямбда-функцией, наблюдайте неожиданное поведение:

>>>

 1 >>> numbers = 'one', 'two', 'three'
 2 >>> funcs = []
 3 >>> for n in numbers:
 4 ...     funcs.append(lambda: print(n))
 5 ...
 6 >>> for f in funcs:
 7 ...     f()
 8 ...
 9 three
10 three
11 three

Неожиданный результат возникает из-за того, что свободная переменнаяn в том виде, в котором она реализована, связана во время выполнения лямбда-выражения. Лямбда-функция Python в строке 4 - это закрытие, которое захватываетn, свободную переменную, привязанную во время выполнения. Во время выполнения при вызове функцииf в строке 7 значениеn равноthree.

Чтобы преодолеть эту проблему, вы можете назначить свободную переменную во время определения следующим образом:

>>>

 1 >>> numbers = 'one', 'two', 'three'
 2 >>> funcs = []
 3 >>> for n in numbers:
 4 ...     funcs.append(lambda n=n: print(n))
 5 ...
 6 >>> for f in funcs:
 7 ...     f()
 8 ...
 9 one
10 two
11 three

Лямбда-функция Python ведет себя как нормальная функция в отношении аргументов. Следовательно, лямбда-параметр может быть инициализирован значением по умолчанию: параметрn принимает внешнийn как значение по умолчанию. Лямбда-функцию Python можно было записать какlambda x=n: print(x) и получить тот же результат.

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

Тестирование Лямбды

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

unittest

Модульunittest обрабатывает лямбда-функции Python аналогично обычным функциям:

import unittest

addtwo = lambda x: x + 2

class LambdaTest(unittest.TestCase):
    def test_add_two(self):
        self.assertEqual(addtwo(2), 4)

    def test_add_two_point_two(self):
        self.assertEqual(addtwo(2.2), 4.2)

    def test_add_three(self):
        # Should fail
        self.assertEqual(addtwo(3), 6)

if __name__ == '__main__':
    unittest.main(verbosity=2)

LambdaTest определяет тестовый пример с тремя методами тестирования, каждый из которых реализует тестовый сценарий дляaddtwo(), реализованный как лямбда-функция. Выполнение файла Pythonlambda_unittest.py, содержащегоLambdaTest, приводит к следующему:

$ python lambda_unittest.py
test_add_three (__main__.LambdaTest) ... FAIL
test_add_two (__main__.LambdaTest) ... ok
test_add_two_point_two (__main__.LambdaTest) ... ok

======================================================================
FAIL: test_add_three (__main__.LambdaTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "lambda_unittest.py", line 18, in test_add_three
    self.assertEqual(addtwo(3), 6)
AssertionError: 5 != 6

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

Как и ожидалось, у нас есть два успешных тестовых примера и один сбой дляtest_add_three: результат5, но ожидаемый результат был6. Этот сбой происходит из-за преднамеренной ошибки в тестовом примере. Изменение ожидаемого результата с6 на5 удовлетворит все тесты дляLambdaTest.

doctest

Модульdoctest извлекает интерактивный код Python изdocstring для выполнения тестов. Хотя синтаксис лямбда-функций Python не поддерживает типичныйdocstring, можно присвоить строку элементу__doc__ именованного лямбда:

addtwo = lambda x: x + 2
addtwo.__doc__ = """Add 2 to a number.
    >>> addtwo(2)
    4
    >>> addtwo(2.2)
    4.2
    >>> addtwo(3) # Should fail
    6
    """

if __name__ == '__main__':
    import doctest
    doctest.testmod(verbose=True)

doctest в комментарии документа лямбдаaddtwo() описывает те же тестовые случаи, что и в предыдущем разделе.

Когда вы выполняете тесты черезdoctest.testmod(), вы получаете следующее:

$ python lambda_doctest.py
Trying:
    addtwo(2)
Expecting:
    4
ok
Trying:
    addtwo(2.2)
Expecting:
    4.2
ok
Trying:
    addtwo(3) # Should fail
Expecting:
    6
**********************************************************************
File "lambda_doctest.py", line 16, in __main__.addtwo
Failed example:
    addtwo(3) # Should fail
Expected:
    6
Got:
    5
1 items had no tests:
    __main__
**********************************************************************
1 items had failures:
   1 of   3 in __main__.addtwo
3 tests in 2 items.
2 passed and 1 failed.
***Test Failed*** 1 failures.

Неудачные результаты теста от того же сбоя, объясненного в выполнении модульных тестов в предыдущем разделе.

Вы можете добавитьdocstring к лямбда-выражению Python через присвоение__doc__ для документирования лямбда-функции. Хотя это возможно, синтаксис Python лучше подходит дляdocstring для обычных функций, чем для лямбда-функций.

Для полного обзора модульного тестирования в Python вы можете обратиться кGetting Started With Testing in Python.

Злоупотребления лямбда-выражениями

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

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

Следующие разделы иллюстрируют несколько примеров использования лямбды, которых следует избегать. Такими примерами могут быть ситуации, когда в контексте лямбда-кода Python код демонстрирует следующий шаблон:

  • Он не следует руководству по стилю Python (PEP 8)

  • Это громоздко и трудно читать.

  • Это излишне умно за счет трудного чтения.

Возникновение исключения

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

>>>

>>> def throw(ex): raise ex
>>> (lambda: throw(Exception('Something bad happened')))()
Traceback (most recent call last):
    File "", line 1, in 
    File "", line 1, in 
    File "", line 1, in throw
Exception: Something bad happened

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

Загадочный стиль

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

Следующий лямбда-пример содержит несколько неудачных стилей:

>>>

>>> (lambda _: list(map(lambda _: _ // 2, _)))([1,2,3,4,5,6,7,8,9,10])
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

Подчеркивание (_) относится к переменной, на которую не нужно ссылаться явно. Но в этом примере три_ относятся к разным переменным. Первоначальным обновлением этого лямбда-кода может быть присвоение имен переменным:

>>>

>>> (lambda some_list: list(map(lambda n: n // 2,
                                some_list)))([1,2,3,4,5,6,7,8,9,10])
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

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

>>>

>>> def div_items(some_list):
      div_by_two = lambda n: n // 2
      return map(div_by_two, some_list)
>>> list(div_items([1,2,3,4,5,6,7,8,9,10])))
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]

Это все еще не оптимально, но показывает вам возможный путь, чтобы сделать код, и в частности лямбда-функции Python, более читабельными. ВAlternatives to Lambdas вы научитесь заменятьmap() иlambda на составные части списка или выражения генератора. Это значительно улучшит читабельность кода.

Классы Python

Вы можете, но не должны писать методы класса как лямбда-функции Python. Следующий пример представляет собой совершенно законный код Python, но демонстрирует нетрадиционный код Python, основанный наlambda. Например, вместо реализации__str__ как обычной функции он используетlambda. Точно так жеbrand иyear - этоproperties, также реализованные с помощью лямбда-функций вместо обычных функций или декораторов:

class Car:
    """Car with methods as lambda functions."""
    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

    brand = property(lambda self: getattr(self, '_brand'),
                     lambda self, value: setattr(self, '_brand', value))

    year = property(lambda self: getattr(self, '_year'),
                    lambda self, value: setattr(self, '_year', value))

    __str__ = lambda self: f'{self.brand} {self.year}'  # 1: error E731

    honk = lambda self: print('Honk!')     # 2: error E731

Запуск такого инструмента, какflake8, инструмента принудительного применения руководства по стилю, отобразит следующие ошибки для__str__ иhonk:

E731 do not assign a lambda expression, use a def

Хотяflake8 не указывает на проблему с использованием лямбда-функций Python в свойствах, их трудно читать и они подвержены ошибкам из-за использования нескольких строк, таких как'_brand' и'_year'с.

Ожидается, что правильная реализация__str__ будет следующей:

def __str__(self):
    return f'{self.brand} {self.year}'

brand можно было бы записать следующим образом:

@property
def brand(self):
    return self._brand

@brand.setter
def brand(self, value):
    self._brand = value

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

Правильное использование лямбда-выражений

Лямбды в Python, как правило, являются предметом споров. Некоторые аргументы против лямбды в Python:

  • Проблемы с читабельностью

  • Наложение функционального мышления

  • Тяжелый синтаксис с ключевым словомlambda

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

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

Классические функциональные конструкции

Лямбда-функции регулярно используются со встроенными функциямиmap() иfilter(), а также сfunctools.reduce(), представленными в модулеfunctools. Следующие три примера являются соответствующими иллюстрациями использования этих функций с лямбда-выражениями в качестве компаньонов:

>>>

>>> list(map(lambda x: x.upper(), ['cat', 'dog', 'cow']))
['CAT', 'DOG', 'COW']
>>> list(filter(lambda x: 'o' in x, ['cat', 'dog', 'cow']))
['dog', 'cow']
>>> from functools import reduce
>>> reduce(lambda acc, x: f'{acc} | {x}', ['cat', 'dog', 'cow'])
'cat | dog | cow'

Возможно, вам придется прочитать код, похожий на приведенные выше примеры, хотя и с более актуальными данными. По этой причине важно распознавать эти конструкции. Тем не менее, эти конструкции имеют эквивалентные альтернативы, которые считаются более Pythonic. ВAlternatives to Lambdas вы узнаете, как преобразовывать функции высшего порядка и соответствующие лямбды в другие, более идиоматические формы.

Ключевые функции

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

  • Метод спискаsort():

  • Встроенные функцииsorted(), min(), max():

  • nlargest() and nsmallest(): в модуле алгоритма очереди кучиheapq

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

Чтобы повлиять на выполнение сортировки, вы можете присвоить лямбду именованному аргументуkey, чтобы при сортировке использовалось число, связанное с идентификатором:

>>>

>>> ids = ['id1', 'id2', 'id30', 'id3', 'id22', 'id100']
>>> print(sorted(ids)) # Lexicographic sort
['id1', 'id2', 'id30', 'id3', 'id22', 'id100']
>>> sorted_ids = sorted(ids, key=lambda x: int(x[2:])) # Integer sort
>>> print(sorted_ids)
['id1', 'id2', 'id3', 'id22', 'id30', 'id100']

UI Frameworks

Структуры пользовательского интерфейса, такие какTkinter,wxPython или .NET Windows Forms сIronPython, используют преимущества лямбда-функций для сопоставления действий в ответ на события пользовательского интерфейса.

Наивная программа Tkinter ниже демонстрирует использованиеlambda, назначенного команде кнопкиReverse:

import tkinter as tk
import sys

window = tk.Tk()
window.grid_columnconfigure(0, weight=1)
window.title("Lambda")
window.geometry("300x100")
label = tk.Label(window, text="Lambda Calculus")
label.grid(column=0, row=0)
button = tk.Button(
    window,
    text="Reverse",
    command=lambda: label.configure(text=label.cget("text")[::-1]),
)
button.grid(column=0, row=1)
window.mainloop()

Нажатие кнопкиReverse запускает событие, которое запускает лямбда-функцию, меняя метку сLambda Calculus наsuluclaC adbmaL *:

Animated TkInter Windows demonstrating the action of the button to the text

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

Чтобы изучить wxPython, посмотритеHow to Build a Python GUI Application With wxPython.

Переводчик Python

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

timeit

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

>>>

>>> from timeit import timeit
>>> timeit("factorial(999)", "from math import factorial", number=10)
0.0013087529951008037

Когда оператор передается в виде строки,timeit() требуется полный контекст. В приведенном выше примере это обеспечивается вторым аргументом, который устанавливает среду, необходимую для основной функции, которая должна быть синхронизирована. В противном случае возникнет исключениеNameError.

Другой подход - использоватьlambda:

>>>

>>> from math import factorial
>>> timeit(lambda: factorial(999), number=10)
0.0012704220062005334

Это решение чище, более читабельно и быстрее вводится в интерпретаторе. Хотя время выполнения было немного меньше для версииlambda, повторное выполнение функций может дать небольшое преимущество для версииstring. Время выполненияsetup исключается из общего времени выполнения и не должно влиять на результат.

Обезьяна Ямочный

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

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

from contextlib import contextmanager
import secrets

def gen_token():
    """Generate a random token."""
    return f'TOKEN_{secrets.token_hex(8)}'

@contextmanager
def mock_token():
    """Context manager to monkey patch the secrets.token_hex
    function during testing.
    """
    default_token_hex = secrets.token_hex
    secrets.token_hex = lambda _: 'feedfacecafebeef'
    yield
    secrets.token_hex = default_token_hex

def test_gen_key():
    """Test the random token."""
    with mock_token():
        assert gen_token() == f"TOKEN_{'feedfacecafebeef'}"

test_gen_key()

Диспетчер контекста помогает изолировать операцию исправления обезьяной функции из стандартной библиотеки (secrets в этом примере). Лямбда-функция, назначеннаяsecrets.token_hex(), заменяет поведение по умолчанию, возвращая статическое значение.

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

Фреймворки модульного тестирования, такие какunittest иpytest, выводят эту концепцию на более высокий уровень сложности.

Сpytest, по-прежнему использующим функциюlambda, тот же пример становится более элегантным и лаконичным:

import secrets

def gen_token():
    return f'TOKEN_{secrets.token_hex(8)}'

def test_gen_key(monkeypatch):
    monkeypatch.setattr('secrets.token_hex', lambda _: 'feedfacecafebeef')
    assert gen_token() == f"TOKEN_{'feedfacecafebeef'}"

Сpytest monkeypatch fixturesecrets.token_hex() перезаписывается лямбда-выражением, которое возвращает детерминированное значениеfeedfacecafebeef, позволяющее проверить тест. Инструмент pytestmonkeypatch позволяет вам контролировать объем переопределения. В приведенном выше примере вызовsecrets.token_hex() в последующих тестах без использования исправлений обезьяны приведет к нормальной реализации этой функции.

Выполнение тестаpytest дает следующий результат:

$ pytest test_token.py -v
============================= test session starts ==============================
platform linux -- Python 3.7.2, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
cachedir: .pytest_cache
rootdir: /home/andre/AB/tools/bpython, inifile:
collected 1 item

test_token.py::test_gen_key PASSED                                       [100%]

=========================== 1 passed in 0.01 seconds ===========================

Тест проходит, поскольку мы подтвердили, чтоgen_token() был выполнен, и результаты были ожидаемыми в контексте теста.

Альтернативы лямбдам

Хотя есть веские причины использоватьlambda, есть случаи, когда его использование осуждается. Так каковы альтернативы?

Функции высшего порядка, такие какmap(),filter() иfunctools.reduce(), могут быть преобразованы в более элегантные формы с небольшими хитростями творчества, в частности, с помощью составления списков или выражений генератора.

ПосмотритеUsing List Comprehensions Effectively, чтобы узнать больше о понимании списков.

Map

Встроенная функцияmap() принимает функцию в качестве первого аргумента и применяет ее к каждому из элементов своего второго аргумента,iterable. Примерами итераций являются строки, списки и кортежи. Дополнительные сведения об итерациях и итераторах см. ВIterables and Iterators.

map() возвращает итератор, соответствующий преобразованной коллекции. Например, если вы хотите преобразовать список строк в новый список с заглавными буквами для каждой строки, вы можете использоватьmap() следующим образом:

>>>

>>> list(map(lambda x: x.capitalize(), ['cat', 'dog', 'cow']))
['Cat', 'Dog', 'Cow']

Вам нужно вызватьlist(), чтобы преобразовать итератор, возвращаемыйmap(), в расширенный список, который можно отобразить в интерпретаторе оболочки Python.

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

>>>

>>> [x.capitalize() for x in ['cat', 'dog', 'cow']]
['Cat', 'Dog', 'Cow']

Фильтр

Встроенная функцияfilter(), еще одна классическая функциональная конструкция, может быть преобразована в представление списка. Он принимаетpredicate в качестве первого аргумента и итерацию в качестве второго аргумента. Он создает итератор, содержащий все элементы начальной коллекции, удовлетворяющие функции предиката. Вот пример, который фильтрует все четные числа в данном списке целых чисел:

>>>

>>> even = lambda x: x%2 == 0
>>> list(filter(even, range(11)))
[0, 2, 4, 6, 8, 10]

Обратите внимание, чтоfilter() возвращает итератор, следовательно, необходимо вызывать встроенный типlist, который создает список с учетом итератора.

Реализация, использующая конструкцию понимания списка, дает следующее:

>>>

>>> [x for x in range(11) if x%2 == 0]
[0, 2, 4, 6, 8, 10]

уменьшить

Начиная с Python 3,reduce() превратился из встроенной функции в функцию модуляfunctools. Как иmap() иfilter(), его первые два аргумента являются, соответственно, функцией и итерацией. Он также может принять инициализатор в качестве третьего аргумента, который используется в качестве начального значения результирующего аккумулятора. Для каждого элемента итерацииreduce() применяет функцию и накапливает результат, который возвращается, когда итерируемый объект исчерпан.

Чтобы применитьreduce() к списку пар и вычислить сумму первого элемента каждой пары, вы можете написать это:

>>>

>>> import functools
>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> functools.reduce(lambda acc, pair: acc + pair[0], pairs, 0)
6

Более идиоматический подход с использованиемgenerator expression в качестве аргумента дляsum() в этом примере следующий:

>>>

>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> sum(x[0] for x in pairs)
6

Немного другое и, возможно, более чистое решение устраняет необходимость явного доступа к первому элементу пары и вместо этого использует распаковку:

>>>

>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> sum(x for x, _ in pairs)
6

Использование подчеркивания (_) - это соглашение Python, указывающее, что вы можете игнорировать второе значение пары.

sum() принимает уникальный аргумент, поэтому выражение генератора не обязательно заключать в круглые скобки.

Лямбда - это пифон или нет?

PEP 8, руководство по стилю кода Python, гласит:

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

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

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

Помимо узкой области применения Pythonlambda,How to Write Beautiful Python Code With PEP 8 - отличный ресурс, который вы, возможно, захотите проверить относительно стиля кода в Python.

Заключение

Теперь вы знаете, как использовать функции Pythonlambda и можете:

  • Написать Python лямбды и использовать анонимные функции

  • Мудро выбирайте между лямбдами или обычными функциями Python

  • Избегайте чрезмерного использования лямбд

  • Используйте лямбда-выражения с функциями высшего порядка или функциями клавиш Python

Если у вас есть склонность к математике, вы можете повеселиться, исследуя увлекательный мирlambda calculus.

Python лямбды похожи на соль. Щепотка вашего спама, ветчины и яиц улучшит вкус, но слишком много испортит блюдо.

__ Take the Quiz: Проверьте свои знания с помощью нашей интерактивной викторины «Лямбда-функции Python». По окончании вы получите оценку, чтобы вы могли отслеживать прогресс в обучении с течением времени:

Note: Язык программирования Python, названный в честь Монти Пайтона, предпочитает использоватьspam,ham иeggs в качестве метасинтаксических переменных вместо традиционныхfoo, bar иbaz.