С лямбда: Лямбда-выражения — Справочник по C# | Microsoft Docs

Содержание

Лямбда-С | справочник Пестициды.ru

Составители: Величко С.Н., Игнатьев П.С.

 

Страница внесена: 12.08.14 17:23

Последнее обновление: 08.06.20 16:02

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

Литературные источники:

1.

Государственный каталог пестицидов и агрохимикатов, разрешенных к применению на территории Российской Федерации, 2012 год. Министерство сельского хозяйства Российской Федерации (Минсельхоз России)

2.

Государственный каталог пестицидов и агрохимикатов, разрешенных к применению на территории Российской Федерации, 2013 год. Министерство сельского хозяйства Российской Федерации (Минсельхоз России)

3.

Государственный каталог пестицидов и агрохимикатов, разрешенных к применению на территории Российской Федерации, 2014 год. Министерство сельского хозяйства Российской Федерации (Минсельхоз России) &nbspСкачать >>>

4.

Государственный каталог пестицидов и агрохимикатов, разрешенных к применению на территории Российской Федерации, 2015 год. Министерство сельского хозяйства Российской Федерации (Минсельхоз России) &nbsp

Скачать >>>

5.

Государственный каталог пестицидов и агрохимикатов, разрешенных к применению на территории Российской Федерации, 2016 год. Министерство сельского хозяйства Российской Федерации (Минсельхоз России) &nbspСкачать >>>

6.

Государственный каталог пестицидов и агрохимикатов, разрешенных к применению на территории Российской Федерации, 2017 год. Министерство сельского хозяйства Российской Федерации (Минсельхоз России) &nbspСкачать >>>

7.

Государственный каталог пестицидов и агрохимикатов, разрешенных к применению на территории Российской Федерации, 2018 год. Министерство сельского хозяйства Российской Федерации (Минсельхоз России) &nbsp

Скачать >>>

Источники из сети интернет:

8.

http://www.rosagrochim.ru/

Свернуть Список всех источников

Лямбды — Kotlin

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

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

Функции высшего порядка

Функция высшего порядка — это функция, которая принимает функции как параметры, или возвращает функцию в качестве результата.

Хорошим примером такой функции является идиома функционального программирования fold для коллекций, которая принимает начальное значение — accumulator вместе с комбинирующей функцией и строит возвращаемое значение, последовательно комбинируя текущее значение accumulator с каждым элементом коллекции, заменяя значение accumulator.

fun <T, R> Collection<T>.fold( initial: R, combine: (acc: R, nextElement: T) -> R ): R { var accumulator: R = initial for (element: T in this) { accumulator = combine(accumulator, element) } return accumulator }

В приведённом выше коде параметр combine имеет функциональный тип (R, T) -> R, поэтому он принимает функцию, которая принимает два аргумента типа R и T и возвращает значение типа R. Он вызывается внутри цикла for и присваивает accumulator возвращаемое значение.

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

fun main() {
    val items = listOf(1, 2, 3, 4, 5)

    // Лямбда - это блок кода, заключенный в фигурные скобки.
    items.fold(0, {
        // Если у лямбды есть параметры, то они указываются перед знаком '->'
        acc: Int, i: Int ->
        print("acc = $acc, i = $i, ")
        val result = acc + i
        println("result = $result")
        // Последнее выражение в лямбде считается возвращаемым значением:
        result
    })

    // Типы параметров в лямбде необязательны, если они могут быть выведены:
    val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })

    // Ссылки на функции также могут использоваться для вызовов функций высшего порядка:
    val product = items.fold(1, Int::times)

    println("joinedToString = $joinedToString")
    println("product = $product")
}

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

Kotlin использует семейство функциональных типов, таких как (Int) -> String, для объявлений, которые являются частью функций: val onClick: () -> Unit = ....

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

  • У всех функциональных типов есть список с типами параметров, заключенный в скобки, и возвращаемый тип: (A, B) -> C
    обозначает тип, который предоставляет функции два принятых аргумента типа A и B, а также возвращает значение типа C. Список с типами параметров может быть пустым, как, например, в () -> A. Возвращаемый тип Unit не может быть опущен;
  • У функциональных типов может быть дополнительный тип — получатель (ориг.: receiver), который указывается в объявлении перед точкой: тип A.(B) -> C описывает функции, которые могут быть вызваны для объекта-получателя A с параметром B и возвращаемым значением C. Литералы функций с объектом-приёмником часто используются вместе с этими типами;
  • Останавливаемые функции (ориг.: suspending functions) принадлежат к особому виду функциональных типов, у которых в объявлении присутствует модификатор suspend, например, suspend () -> Unit или suspend A.(B) -> C.

Объявление функционального типа также может включать именованные параметры: (x: Int, y: Int) -> Point. Именованные параметры могут быть использованы для описания смысла каждого из параметров.

Чтобы указать, что функциональный тип может быть nullable, используйте круглые скобки: ((Int, Int) -> Int)?.

При помощи круглых скобок функциональные типы можно объединять:

(Int) -> ((Int) -> Unit).

Стрелка в объявлении является правоассоциативной (ориг.: right-associative), т.е. объявление (Int) -> (Int) -> Unit эквивалентно объявлению из предыдущего примера, а не ((Int) -> (Int)) -> Unit.

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

typealias ClickHandler = (Button, ClickEvent) -> Unit

Создание функционального типа

Существует несколько способов получить экземпляр функционального типа:

  • Используя вызываемую ссылку на существующее объявление:

    К ним относятся привязанные вызываемые ссылки, которые указывают на член конкретного экземпляра: foo::toString.

  • Используя экземпляр пользовательского класса, который реализует функциональный тип в качестве интерфейса:
class IntTransformer: (Int) -> Int {
    override operator fun invoke(x: Int): Int = TODO()
}

val intFunction: (Int) -> Int = IntTransformer()

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

val a = { i: Int -> i + 1 } // Выведенный тип - (Int) -> Int

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

(A, B) -> C может быть передано или назначено там, где ожидается A.(B) -> C, и наоборот.

fun main() {
    val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
    val twoParameters: (String, Int) -> String = repeatFun // OK

    fun runTransformation(f: (String, Int) -> String): String {
        return f("hello", 3)
    }
    val result = runTransformation(repeatFun) // OK

    println("result = $result")
}

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

Вызов экземпляра функционального типа

Значение функционального типа может быть вызвано с помощью оператора invoke(...): f.invoke(x) или просто f(x).

Если значение имеет тип получателя, то объект-приёмник должен быть передан в качестве первого аргумента. Другой способ вызвать значение функционального типа с получателем — это добавить его к объекту-приёмнику, как если бы это была функция-расширение: 1.foo(2).

Пример:

fun main() {
    val stringPlus: (String, String) -> String = String::plus
    val intPlus: Int.(Int) -> Int = Int::plus

    println(stringPlus.invoke("<-", "->"))
    println(stringPlus("Hello, ", "world!"))

    println(intPlus.invoke(1, 1))
    println(intPlus(1, 2))
    println(2.intPlus(3)) // вызывается как функция-расширение
}

Встроенные функции

Иногда выгодно улучшить производительность функций высшего порядка, используя встроенные функции (ориг.: inline functions).

Лямбда-выражения и анонимные функции

Лямбда-выражения и анонимные функции — это «функциональный литерал», то есть необъявленная функция, которая немедленно используется в качестве выражения. Рассмотрим следующий пример:

max(strings, { a, b -> a.length < b.length })

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

fun compare(a: String, b: String): Boolean = a.length < b.length

Синтаксис лямбда-выражений

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

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
  • Лямбда-выражение всегда заключено в скобки {...};
  • Объявление параметров при таком синтаксисе происходит внутри этих скобок и может включать в себя аннотации типов;
  • Тело функции начинается после знака ->;
  • Если тип возвращаемого значения не Unit, то в качестве возвращаемого типа принимается последнее (а возможно и единственное) выражение внутри тела лямбды.

Если вы вынесите все необязательные объявления, то, что останется, будет выглядеть следующим образом:

val sum = { x: Int, y: Int -> x + y }

Передача лямбды в качестве последнего параметра

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

val product = items.fold(1) { acc, e -> acc * e }

Такой синтаксис также известен как trailing lambda.

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

run { println("...") }

it: неявное имя единственного параметра

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

Если компилятор способен самостоятельно определить сигнатуру, то объявление параметра можно опустить вместе с ->. Параметр будет неявно объявлен под именем it.

ints.filter { it > 0 } // этот литерал имеет тип '(it: Int) -> Boolean'

Возвращение значения из лямбда-выражения

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

Таким образом, два следующих фрагмента равнозначны:

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0
    [email protected] shouldFilter
}

Это соглашение, вместе с передачей лямбда-выражения вне скобок, позволяет писать код в стиле LINQ.

strings.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }

Символ подчеркивания для неиспользуемых переменных

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

map.forEach { _, value -> println("$value!") }

Деструктуризация в лямбдах

Деструктуризация в лямбдах описана в Деструктурирующие объявления.

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

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

fun(x: Int, y: Int): Int = x + y

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

fun(x: Int, y: Int): Int {
    return x + y
}

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

ints.filter(fun(item) = item > 0)

Аналогично и с типом возвращаемого значения: он вычисляется автоматически для функций-выражений или же должен быть явно определён (если не является типом Unit) для анонимных функций с блоком в качетсве тела.

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

Одним из отличий лямбда-выражений от анонимных функций является поведение оператора return (non-local returns). Слово return, не имеющее метки (@), всегда возвращается из функции, объявленной ключевым словом fun. Это означает, что return внутри лямбда-выражения возвратит выполнение к функции, включающей в себя это лямбда-выражение. Внутри анонимных функций оператор return, в свою очередь, выйдет, собственно, из анонимной функции.

Замыкания

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

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

Литералы функций с объектом-приёмником

Функциональные типы с получателем, такие как A.(B) -> C, могут быть вызваны с помощью особой формы – литералов функций с объектом-приёмником.

Как было сказано выше, Kotlin позволяет вызывать экземпляр функционального типа с получателем, предоставляющим объект-приёмник.

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

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

Ниже приведён пример литерала с получателем вместе с его типом, где plus вызывается для объекта-приёмника:

val sum: Int.(Int) -> Int = { other -> plus(other) }

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

val sum = fun Int.(other: Int): Int = this + other

Лямбда-выражения могут быть использованы как литералы функций с приёмником, когда тип приёмника может быть выведен из контекста. Один из самых важных примеров их использования это типобезопасные строители (ориг.: type-safe builders).

class HTML {
    fun body() { ... }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML() // создание объекта-приёмника
    html.init()       // передача приёмника в лямбду
    return html
}


html {     // лямбда с приёмником начинается тут
    body() // вызов метода объекта-приёмника
}

Изучаем лямбда-выражения в Котлин для начинающих

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

Содержание статьи

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

Основы лямбда-выражений в Kotlin

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

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

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

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

Далее дано объявление переменной, которая может содержать лямбду:

fun main() { var multiplyLambda: (Int, Int) -> Int }

fun main() {

    var multiplyLambda: (Int, Int) -> Int

}

Lambda multiplyLambda принимает два значения типа Int и возвращает Int. Обратите внимание, что это то же самое, что объявление переменной для функции. Как было сказано, лямбда — это просто функция без названия. Типом лямбды является тип функции.

Лямбда присваивается переменной следующим образом:

fun main() { var multiplyLambda = { a: Int, b: Int -> Int a * b } }

fun main() {

    var multiplyLambda = { a: Int, b: Int -> Int

        a * b

    }

}

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

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

fun main() { var multiplyLambda = { a: Int, b: Int -> Int a * b } val lambdaResult = multiplyLambda(4, 2) println(lambdaResult) // Результат: 8 }

fun main() {

    var multiplyLambda = { a: Int, b: Int -> Int

        a * b

    }

 

    val lambdaResult = multiplyLambda(4, 2)

    println(lambdaResult) // Результат: 8

}

Как и следовало ожидать, lambdaResult равен 8. И вновь есть небольшая разница. Лямбда не разрешает использовать названия для аргументов. К примеру, вы не можете написать multiplyLambda(a = 4, b = 2). В отличие от обычных функций, вы не можете использовать именованные аргументы при вызове lambda.

Сокращенный синтаксис для lambda-выражения

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

multiplyLambda = { a, b -> a * b }

multiplyLambda = { a, b ->

    a * b

}

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

Применение it в лямбда-выражении

Лямбду, у которой только один параметр, можно сократить, используя ключевое слово it:

fun main() { var doubleLambda = { a: Int -> 2 * a } println(doubleLambda(4)) // Вывод: 8 }

fun main() {

    var doubleLambda = { a: Int ->

        2 * a

    }

 

    println(doubleLambda(4)) // Вывод: 8

}

Так как здесь только один параметр и тип лямбды определен, синтаксис её применения может быть сокращен до такого вида:

doubleLambda = { 2 * it }

doubleLambda = { 2 * it }

Код выше считается устаревшим. Применение it при работе с лямбда-выражением выглядит следующим образом:

fun main() { val doubleLambda: (Int) -> Int = { 2 * it } println(doubleLambda(4)) // Вывод: 8 }

fun main() {

    val doubleLambda: (Int) -> Int = { 2 * it }

 

    println(doubleLambda(4)) // Вывод: 8

}

Лямбда в качестве аргумента для функции

Рассмотрим следующий код:

fun operateOnNumbers( a: Int, b: Int, operation: (Int, Int) -> Int ): Int { val result = operation(a, b) println(result) return result }

fun operateOnNumbers(

    a: Int,

    b: Int,

    operation: (Int, Int) -> Int

): Int {

    val result = operation(a, b)

    println(result)

    return result

}

Код объявляет функцию operateOnNumbers, которая принимает первые два параметра типа Int. Третий параметр называется operation, и он имеет тип функции.

Функция operateOnNumbers возвращает результат который имеет тип Int.

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

fun operateOnNumbers( a: Int, b: Int, operation: (Int, Int) -> Int ): Int { val result = operation(a, b) println(result) return result } fun main() { val addLambda = { a: Int, b: Int -> a + b } operateOnNumbers(4, 2, operation = addLambda) // Вывод: 6 }

fun operateOnNumbers(

    a: Int,

    b: Int,

    operation: (Int, Int) -> Int

): Int {

    val result = operation(a, b)

    println(result)

    return result

}

 

fun main() {

    val addLambda = { a: Int, b: Int ->

        a + b

    }

    operateOnNumbers(4, 2, operation = addLambda) // Вывод: 6

}

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

fun operateOnNumbers( a: Int, b: Int, operation: (Int, Int) -> Int ): Int { val result = operation(a, b) println(result) return result } fun addFunction(a: Int, b:Int) = a + b fun main() { operateOnNumbers(4, 2, operation = ::addFunction) // Вывод: 6 }

fun operateOnNumbers(

    a: Int,

    b: Int,

    operation: (Int, Int) -> Int

): Int {

    val result = operation(a, b)

    println(result)

    return result

}

 

fun addFunction(a: Int, b:Int) = a + b

 

fun main() {

    operateOnNumbers(4, 2, operation = ::addFunction) // Вывод: 6

}

Функция operateOnNumbers вызывается одинаково, будь значение из operation функцией или лямбдой. Оператор :: является ссылочным оператором. В данном случае он дает указание коду найти функцию addFunction в текущей области видимости.

Сила лямбда-синтаксиса нам снова пригодится.

Можно определить встроенную лямбду с помощью вызова функции operationOnNumbers следующим образом:

fun main() { operateOnNumbers(4, 2, operation = {a: Int, b: Int -> a + b }) }

fun main() {

    operateOnNumbers(4, 2, operation = {a: Int, b: Int ->

        a + b

    })

}

Не нужно определять лямбду и назначать её локальной переменной или константе. Можно просто объявить лямбду прямо там, где вы передаете ее в функцию в качестве аргумента!

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

fun main() { operateOnNumbers(4, 2) {a, b -> a + b } }

fun main() {

    operateOnNumbers(4, 2) {a, b ->

        a + b

    }

}

Можно пойти дальше. Оператор + является оператором функции plus() от класса Int, которая принимает два аргумента и возвращает один результат, поэтому вы можете написать:

fun main() { operateOnNumbers(4, 2, operation = Int::plus) }

fun main() {

    operateOnNumbers(4, 2, operation = Int::plus)

}

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

fun main() { operateOnNumbers(4, 2) {a, b -> a + b } }

fun main() {

    operateOnNumbers(4, 2) {a, b ->

        a + b

    }

}

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

Лямбды без возвращаемого значения

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

fun main() { val unitLambda: () -> Unit = { println(«Kotlin классный!») } unitLambda() }

fun main() {

    val unitLambda: () -> Unit = {

        println(«Kotlin классный!»)

    }

 

    unitLambda()

}

Типом лямбды является () -> Unit. Пустые скобки означают отсутствие параметров. Вы должны объявить возвращаемый тип, чтобы Kotlin знал, что объявляется лямбду. Именно здесь пригодится тип Unit, когда лямбда не должна возвращать никакого важного значения.

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

fun main() { val nothingLambda: () -> Nothing = { throw NullPointerException() } nothingLambda() }

fun main() {

    val nothingLambda: () -> Nothing = { throw NullPointerException() }

 

    nothingLambda()

}

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

Область видимости замыкания (Closure)

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

На заметку: Напомним, что область видимости определяет диапазон, в котором доступна сущность (переменная, константа и т.д.). Вы видели, как новая область видимости создается внутри if-оператора. Лямбды также вводят новую область видимости и наследуют все сущности, видимые в области видимости, в которой они определены.

Рассмотрим следующую лямбду:

fun main() { var counter = 0 val incrementCounter = { counter += 1 } }

fun main() {

    var counter = 0

 

    val incrementCounter = {

        counter += 1

    }

}

Лямбда incrementCounter довольно простая: она увеличивает переменную counter на одну единицу. Переменная counter определяется вне лямбды. Лямбда может получить доступ к переменной, потому что лямбда определена в той же области, что и переменная. В таком случае, лямбда захватывает переменную counter. Любые изменения, которые вносятся в переменную, видны как внутри лямбды, так и за её пределами.

Допустим, вы вызываете лямбду пять раз следующим образом:

fun main() { var counter = 0 val incrementCounter = { counter += 1 } incrementCounter() incrementCounter() incrementCounter() incrementCounter() incrementCounter() }

fun main() {

    var counter = 0

    val incrementCounter = {

        counter += 1

    }

 

    incrementCounter()

    incrementCounter()

    incrementCounter()

    incrementCounter()

    incrementCounter()

}

После данных пяти вызовов — переменная counter будет равна 5.

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

fun countingLambda(): () -> Int { var counter = 0 val incrementCounter: () -> Int = { counter += 1 counter } return incrementCounter }

fun countingLambda(): () -> Int {

    var counter = 0

    val incrementCounter: () -> Int = {

        counter += 1

        counter

    }

    return incrementCounter

}

Данная функция не принимает параметров и возвращает лямбду. Возвращаемая лямбда не принимает параметров и возвращает Int.

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

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

fun countingLambda(): () -> Int { var counter = 0 val incrementCounter: () -> Int = { counter += 1 counter } return incrementCounter } fun main() { val counter1 = countingLambda() val counter2 = countingLambda() println(counter1()) // > 1 println(counter2()) // > 1 println(counter1()) // > 2 println(counter1()) // > 3 println(counter2()) // > 2 }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

fun countingLambda(): () -> Int {

    var counter = 0

    val incrementCounter: () -> Int = {

        counter += 1

        counter

    }

    return incrementCounter

}

 

fun main() {

    val counter1 = countingLambda()

    val counter2 = countingLambda()

 

    println(counter1()) // > 1

    println(counter2()) // > 1

    println(counter1()) // > 2

    println(counter1()) // > 3

    println(counter2()) // > 2

}

Два экземпляра элемента counter, созданные функцией, являются взаимно исключающими и считаются независимо друг от друга — так как имеют разные области видимости. Здорово!

Сортировка данных при помощи Lambda

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

Вы вызываете метод sort() для получения отсортированного массива следующим образом:

fun main() { val names = arrayOf(«ZZZZZZ», «BB», «A», «CCCC», «EEEEE») names.sort() for (item in names) { println(item) } }

fun main() {

    val names = arrayOf(«ZZZZZZ», «BB», «A», «CCCC», «EEEEE»)

    names.sort()

 

    for (item in names) {

        println(item)

    }

}

Результат:

Указав нашу собственную лямбду методу compareBy() в качестве функции для сортировки, которая возвращает Comparator для метода sortedWith(), можно изменить условия сортировки массива.

Передайте лямбду в метод compareBy() следующим образом:

fun main() { val names = arrayOf(«ZZZZZZ», «BB», «A», «CCCC», «EEEEE») val namesByLength = names.sortedWith(compareBy { -it.length }) for (item in namesByLength) { println(item) } }

fun main() {

    val names = arrayOf(«ZZZZZZ», «BB», «A», «CCCC», «EEEEE»)

    val namesByLength = names.sortedWith(compareBy {

        -it.length

    })

    

    for (item in namesByLength) {

        println(item)

    }

}

Результат:

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

Итерация по коллекциям с лямбда-выражениями

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

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

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

fun main() { val values = listOf(1, 2, 3, 4, 5, 6) values.forEach { println(«$it: ${it * it}») } }

fun main() {

    val values = listOf(1, 2, 3, 4, 5, 6)

    values.forEach {

        println(«$it: ${it * it}»)

    }

}

Результат:

1: 1 2: 4 3: 9 4: 16 5: 25 6: 36

1: 1

2: 4

3: 9

4: 16

5: 25

6: 36

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

Другая функция позволяет отфильтровать определенные элементы которые соответствую определенному условию:

fun main() { val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19) val largePrices = prices.filter { it > 5.0 } println(largePrices) // Результат: [10.0, 8.19] }

fun main() {

    val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19)

 

    val largePrices = prices.filter {

        it > 5.0

    }

 

    println(largePrices) // Результат: [10.0, 8.19]

}

Здесь создается список значений типа Double для обозначения цен товаров в магазине. Для фильтрации цен на продукты которые превышают 5 долларов можно использовать функцию filter.

Данная функция выглядит следующим образом:

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>

Это означает, что функция filter принимает один параметр под названием predicate, который является лямбдой (или функцией), которая принимает T и возвращает Boolean значение. Затем функция filter возвращает список T. В этом контексте T относится к типу элементов в списке. В приведенном выше примере это тип Double.

Задача лямбды которую она выполняет внутри filter это вернуть true или false в зависимости от того, следует ли сохранить значение или пропустить её. Список, возвращаемый из filter, будет содержать все элементы, для которых лямбда вернула true.

В данном примере largePrices будет содержать:

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

Однако это еще не все!

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

fun main() { val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19) val salePrices = prices.map { it * 0.9 } println(salePrices) }

fun main() {

    val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19)

 

    val salePrices = prices.map {

        it * 0.9

    }

 

    println(salePrices)

}

Функция map принимает лямбду и выполняет её для каждого элемента в списке и вернет новый список, содержащий элементы которые прошли проверку из лямбды. В этом случае salePrices будет содержать:

[1.35, 9.0, 4.4910000000000005, 2.07, 7.3709999999999996]

[1.35, 9.0, 4.4910000000000005, 2.07, 7.3709999999999996]

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

Изменение типа элементов внутри списка

Функцию map также можно использовать для изменения типа элементов внутри списка. Это делается следующим образом:

fun main() { val userInput = listOf(«0», «11», «haha», «42») val numbers = userInput.map { it.toIntOrNull() } println(numbers) // Вывод: [0, 11, null, 42] }

fun main() {

    val userInput = listOf(«0», «11», «haha», «42»)

    val numbers = userInput.map {

        it.toIntOrNull()

    }

 

    println(numbers) // Вывод: [0, 11, null, 42]

}

Мы имеем в константе userInput небольшой список со строками, и нам нужно превратить их в тип Int? если там действительно числа или в null если там обычный текст.

Если вы хотите отфильтровать данные и избавиться от null значений, тогда можно использовать метод mapNotNull() следующим образом:

fun main() { val userInput = listOf(«0», «11», «haha», «42») val ignoreNull = userInput.mapNotNull { it.toIntOrNull() } println(ignoreNull) // Вывод: [0, 11, 42] }

fun main() {

    val userInput = listOf(«0», «11», «haha», «42»)

 

    val ignoreNull = userInput.mapNotNull {

        it.toIntOrNull()

    }

    

    println(ignoreNull) // Вывод: [0, 11, 42]

}

Это почти то же самое, что и map, за исключением того, что игнорируются null значения.

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

Её можно использовать над списком price для расчета результата:

fun main() { val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19) var sum = prices.fold(0.0) { a, b -> a + b } println(sum) }

fun main() {

    val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19)

 

    var sum = prices.fold(0.0) { a, b ->

        a + b

    }

 

    println(sum)

}

Начальное значение равна 0.0. Затем лямбда подсчитывает сумму текущего значения плюс следующий значения из списка. Таким образом, вы вычисляете сумму всех значений в списке. В этом случае значение из переменной sum будет:

Функция reduce тесно связана с fold. В Kotlin reduce использует первый элемент из коллекции как начальное значение:

fun main() { val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19) var sum = prices.reduce { a, b -> a + b } println(sum) # Вывод: 26.980000000000004 }

fun main() {

    val prices = listOf(1.5, 10.0, 4.99, 2.30, 8.19)

 

    var sum = prices.reduce { a, b ->

        a + b

    }

 

    println(sum) # Вывод: 26.980000000000004

}

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

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

fun main() { val stock = mapOf(1.5 to 5, 10.0 to 2, 4.99 to 20, 2.30 to 5, 8.19 to 30) var stockSum = 0.0 stock.forEach { stockSum += it.key * it.value } println(stockSum) }

fun main() {

    val stock = mapOf(1.5 to 5, 10.0 to 2, 4.99 to 20, 2.30 to 5, 8.19 to 30)

    var stockSum = 0.0

 

    stock.forEach {

        stockSum += it.key * it.value

    }

 

    println(stockSum)

}

В данном случае, параметром функции forEach является Map.Entry, он содержит key и value от элементов карты.

Вот результат:

println(stockSum) // > 384.5

println(stockSum) // > 384.5

На этом завершается раздел по изучению способов итерации данных при помощи лямбда-выражений!

Задачи для проверки

Выполните задания ниже, чтобы проверить, как хорошо вы усвоили лямбда-выражения в Kotlin.

Задача №1 — Создайте константу nameList, которая содержит список из строк с именами. Подойдут любые имена — убедитесь, что у вас в списке больше трёх имён. Теперь используйте функцию fold, чтобы создать новую строку, которая содержим все имена из списка склеенными вместе.

Решение задачи №1


fun main() { val nameList = listOf(«Anna», «Bob», «Craig», «Donna») var namesConcat = nameList.fold(«») { a, b -> a + b } println(namesConcat) // Вывод: AnnaBobCraigDonna }

fun main() {

    val nameList = listOf(«Anna», «Bob», «Craig», «Donna»)

 

    var namesConcat = nameList.fold(«») { a, b ->

        a + b

    }

 

    println(namesConcat) // Вывод: AnnaBobCraigDonna

}



[crayon-6251dd0eae064936084513/]

Задача №2 — Используя тот же список nameList, сначала отфильтруйте список, чтобы он содержал только имена, в которых содержится более четырех символов, а затем создайте такую же склейку из имен, как в приведенном выше задании.

Решение задачи №2


fun main() { val nameList = listOf(«Anna», «Bob», «Craig», «Donna») val namesConcat = nameList.filter { it.length > 4 }.fold(«») { a, b -> a + b } println(namesConcat) // Вывод: CraigDonna }

fun main() {

    val nameList = listOf(«Anna», «Bob», «Craig», «Donna»)

    

    val namesConcat = nameList.filter {

        it.length > 4

    }.fold(«») { a, b ->

        a + b

    }

    println(namesConcat) // Вывод: CraigDonna

}



[crayon-6251dd0eae066982208608/]

Задача №3 — Создайте константу которая будет содержать карту под названием namesAndAges, которая содержит имена, сопоставленные по возрасту, возраст имеет тип integer. Теперь используйте функцию filter, чтобы создать новую карту, содержащую только людей младше 18 лет.

Решение задачи №3


fun main() { val namesAndAges = mapOf( «Anna» to 16, «Bob» to 35, «Craig» to 15, «Donna» to 28 ) val youngPeople = namesAndAges.filter { it.value < 18 } println(youngPeople) // Вывод: {Anna=16, Craig=15} }

fun main() {

    val namesAndAges = mapOf(

        «Anna» to 16,

        «Bob» to 35,

        «Craig» to 15,

        «Donna» to 28

    )

 

    val youngPeople = namesAndAges.filter {

        it.value < 18

    }

    

    println(youngPeople) // Вывод: {Anna=16, Craig=15}

}



[crayon-6251dd0eae068233842744/]

Задача №4 — Используя карту namesAndAges, отфильтруйте взрослых людей (от 18 лет и старше), а затем используйте функцию map для преобразования её в список, содержащий только имена, то есть можно убрать возраст.

Решение задачи №4


fun main() { val namesAndAges = mapOf( «Anna» to 16, «Bob» to 35, «Craig» to 15, «Donna» to 28 ) val youngPeopleNames = namesAndAges.filter { it.value < 18 }.map { it.key } println(youngPeopleNames) // Вывод: [Anna, Craig] }

fun main() {

    val namesAndAges = mapOf(

        «Anna» to 16,

        «Bob» to 35,

        «Craig» to 15,

        «Donna» to 28

    )

 

    val youngPeopleNames = namesAndAges.filter {

        it.value < 18

    }.map {

        it.key

    }

    

    println(youngPeopleNames) // Вывод: [Anna, Craig]

}



[crayon-6251dd0eae06b153671340/]

Повторите функцию несколько раз

Задача №5 — Напишите функцию, которая запускает лямбду task() столько раз сколько будет указано в параметре time.

Объявите функцию следующим образом:

fun repeatTask(times: Int, task: () -> Unit)

fun repeatTask(times: Int, task: () -> Unit)

Используйте данную функцию для вывода предложения "Kotlin классный!" 10 раз.

Решение задачи №5


fun main() { fun repeatTask(times: Int, task: () -> Unit) { for (time in 0 until times) { task() } } repeatTask(10) { println(«Kotlin классный!») } }

fun main() {

    fun repeatTask(times: Int, task: () -> Unit) {

        for (time in 0 until times) {

            task()

        }

    }

 

    repeatTask(10) {

        println(«Kotlin классный!»)

    }

}



[crayon-6251dd0eae06f515627455/]

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

Объявите функцию следующим образом:

fun mathSum(length: Int, series: (Int) -> Int) -> Int

fun mathSum(length: Int, series: (Int) -> Int) -> Int

Первый параметр length, определяет количество значений для суммирования. Второй параметр series представляет собой лямбду, которую можно использовать для создания серии значений. Лямбда series должна принимать параметр, который является позицией значения в серии, и возвращать значение по этой позиции.

Функция mathSum должна вычислить и вернуть сумму значений от полученному из lambda-выражения результату в зависимости от указанному интервалу из lenght.

  • Используйте данную функцию, чтобы найти сумму первых 10 квадратных чисел, которая равна 385.
  • Затем используйте функцию, чтобы найти сумму первых 10 чисел Фибоначчи, которая равна 143.

Решение задачи №6


fun mathSum(length: Int, series: (Int) -> Int): Int { var result = 0 for (i in 0..length) { result += series(i) } return result } fun fibonacci(number: Int): Int { if (number <= 0) { return 0 } if (number == 1 || number == 2) { return 1 } return fibonacci(number — 1) + fibonacci(number — 2) } fun main() { println(mathSum(10) { it * it }) // Вывод: 385 println(mathSum(10, ::fibonacci)) // Вывод: 143 }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

fun mathSum(length: Int, series: (Int) -> Int): Int {

    var result = 0

    for (i in 0..length) {

        result += series(i)

    }

    return result

}

 

fun fibonacci(number: Int): Int {

    if (number <= 0) {

        return 0

    }

    if (number == 1 || number == 2) {

        return 1

    }

    return fibonacci(number — 1) + fibonacci(number — 2)

}

 

fun main() {

    println(mathSum(10) { it * it }) // Вывод: 385

    println(mathSum(10, ::fibonacci)) // Вывод: 143

}



[crayon-6251dd0eae072215243047/]

Задача №7 — В данном задании вам предоставляется список названий приложений с рейтингами которые им давались. Это выдуманные приложения.

Вот как выглядит карта с приложениями:

fun main() { val appRatings = mapOf( «Calendar Pro» to arrayOf(1, 5, 5, 4, 2, 1, 5, 4), «The Messenger» to arrayOf(5, 4, 2, 5, 4, 1, 1, 2), «Socialise» to arrayOf(2, 1, 2, 2, 1, 2, 4, 2) ) }

fun main() {

    val appRatings = mapOf(

        «Calendar Pro» to arrayOf(1, 5, 5, 4, 2, 1, 5, 4),

        «The Messenger» to arrayOf(5, 4, 2, 5, 4, 1, 1, 2),

        «Socialise» to arrayOf(2, 1, 2, 2, 1, 2, 4, 2)

    )

}

Сначала создайте новую карту averageRatings, которая содержит названия приложений и их средний рейтинг. Используйте функцию forEach для итерации по карте appRatings, затем используйте функцию reduce для вычисления среднего рейтинга приложений и сохраните результат в карте averageRatings.

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

Решение задачи №7


fun main() { val appRatings = mapOf( «Calendar Pro» to arrayOf(1, 5, 5, 4, 2, 1, 5, 4), «The Messenger» to arrayOf(5, 4, 2, 5, 4, 1, 1, 2), «Socialise» to arrayOf(2, 1, 2, 2, 1, 2, 4, 2) ) // Вычисляем средний рейтинг приложений. val averageRatings = mutableMapOf<String, Double>() appRatings.forEach { val total = it.value.reduce(Int::plus) // + is a function too! averageRatings[it.key] = total.toDouble() / it.value.size } println(averageRatings) // Вывод: {Calendar Pro=3.375, The Messenger=3.0, Socialise=2.0} // Список приложений чей рейтинг выше тройки. val goodApps = averageRatings.filter { it.value > 3 }.map { it.key } println(goodApps) // Вывод: [Calendar Pro] }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

fun main() {

    val appRatings = mapOf(

        «Calendar Pro» to arrayOf(1, 5, 5, 4, 2, 1, 5, 4),

        «The Messenger» to arrayOf(5, 4, 2, 5, 4, 1, 1, 2),

        «Socialise» to arrayOf(2, 1, 2, 2, 1, 2, 4, 2)

    )

 

    // Вычисляем средний рейтинг приложений.

    val averageRatings = mutableMapOf<String, Double>()

    appRatings.forEach {

        val total = it.value.reduce(Int::plus) // + is a function too!

        averageRatings[it.key] = total.toDouble() / it.value.size

    }

 

    println(averageRatings) // Вывод: {Calendar Pro=3.375, The Messenger=3.0, Socialise=2.0}

 

    // Список приложений чей рейтинг выше тройки.

    val goodApps = averageRatings.filter {

        it.value > 3

    }.map {

        it.key

    }

 

    println(goodApps) // Вывод: [Calendar Pro]

}



[crayon-6251dd0eae078706065367/]

Ключевые особенности лямбда-выражений

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

Что дальше?

Лямбды и функции являются фундаментальными типами для хранения и повторного использования важных фрагментов кода. Помимо объявления и вызова lambda — вы узнали как их можно передавать в качестве аргумента другим функциям — тем самым расширяя возможности обычным функциям.

В следующей статье мы поговорим о создании своих собственных типов данных.

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

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

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

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

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

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

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

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

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

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

История

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

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

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

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

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

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

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

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

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

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

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

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

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

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

>>> lambda x: x

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

  • Ключевое слово: lambda
  • Связанная переменная: x
  • Тело: х

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

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

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

>>> lambda x: x + 1

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

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

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

(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, принимает два аргумента и возвращает строку, интерполирующую два параметра: первый и последний. Как и ожидалось, определение лямбды перечисляет аргументы без скобок, тогда как вызов функции выполняется точно так же, как и обычная функция Python, с круглыми скобками вокруг аргументов.

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

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

  • Анонимные функции
  • Лямбда-функции
  • Лямбда-выражения
  • Лямбда-абстракции
  • Лямбда-форма
  • Функциональные литералы

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

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

>>> lambda x, y: x + y

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

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

>>> _(1, 2)
3

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

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

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

Для получения более подробной информации об использовании этого специального символа в Python, посмотрите Значение подчеркивания в Python (The Meaning of Underscores in Python).

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

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

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

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

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

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

>>> 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 Design and History FAQ, похоже, задает тон в отношении общего ожидания использования лямбда-функций в Python:

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

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

Функции

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

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

>>> import dis
>>> add = lambda x, y: x + y
>>> type(add)
<class 'function'>
>>> dis.dis(add)
  1           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> add
<function <lambda> at 0x7f30c6ce9ea0>

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

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

>>> import dis
>>> def add(x, y): return x + y
>>> type(add)
<class 'function'>
>>> dis.dis(add)
  1           0 LOAD_FAST                0 (x)
              2 LOAD_FAST                1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> add
<function add at 0x7f30c6ce9f28>

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

Traceback

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

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

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

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

>>> def div_zero(x): return x / 0
>>> div_zero(2)
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", 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 "<input>", line 1
    (lambda x: assert x == 2)(2)
                    ^
SyntaxError: invalid syntax

Этот надуманный пример демонстрирующий что с помощью assert, утверждается что параметр x имеет значение 2. Но интерпретатор выдает SyntaxError при синтаксическом анализе кода, который включает в себя утверждение assert в теле лямбда-выражения.

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

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

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

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

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

Если вы начали применять анотации типов, которые теперь доступны в Python, у вас есть еще одна веская причина предпочесть нормальные функции лямбда-функциям 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 "<stdin>", line 1
    lambda first: str, last: str: first.title() + " " + last.title() -> str

SyntaxError: invalid syntax

IIFE

Вы уже видели несколько примеров немедленного запуска функции:

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

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

Аргументы

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

  • Позиционные аргументы
  • Именованные аргументы (иногда называемые ключевыми аргументами)
  • Переменный список аргументов (часто называемый 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

Декораторы

В Python декоратор — это реализация шаблона, который позволяет добавить поведение к функции или классу. Обычно это выражается синтаксисом @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(2) получается следующий результат:

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: <lambda>, args: (3,), kwargs: {}
9

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

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

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

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

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

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

Замыкание

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

Понятия лямбды и замыкания не обязательно связаны, хотя лямбда-функции могут быть замыканиями так же, как обычные функции также могут быть замыканиями. Некоторые языки имеют специальные конструкции для замыкания или лямбды (например, 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(), привязывается к имени замыкания. В строке 5 inner_func() захватывает x и y, потому что он имеет доступ к своей области видимости, так что при вызове замыкания он может работать с двумя свободными переменными x и y.

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

def outer_func(x):
    y = 4
    return lambda z: x + y + z

for i in range(3):
    closure = outer_func(i)
    print(f"closure({i+5}) = {closure(i+5)}")

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

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

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

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

Время оценки

В некоторых ситуациях, связанных с циклами, поведение лямбда-функции 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

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

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

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

Лямбды можно тестировать аналогично обычным функциям. Можно использовать как 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(), реализованной как лямбда-функция. Выполнение Python-файла lambda_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 в комментарии к функции lambda 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 к лямбда-выражению через присвоение __doc__ для документирования лямбда-функции. Хотя это возможно, синтаксис docstring все же лучше использовать для нормальных функций, а не для лямбда-функции.

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

Несколько примеров в этой статье, если они написаны в контексте профессионального кода 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 "<stdin>", line 1, in <module>
    File "<stdin>", line 1, in <lambda>
    File "<stdin>", 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]

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

>>> 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, более удобочитаемых. В разделе Альтернативы лямбда-выражениям вы научитесь заменять map() и лямбда-выражения на списки или выражения-генераторы. Это значительно улучшит читабельность кода.

Классы Python

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

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 не указывает на проблему использования лямбда-функций в свойствах, их трудно читать и они подвержены ошибкам из-за использования нескольких строк, таких как _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. В разделе Альтернативы лямбдам вы узнаете, как преобразовывать функции высшего порядка и сопровождающие их лямбды в другие, более идиоматические формы.

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

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

  • sort(): метод списка
  • sorted()min()max(): встроенные функции
  • nlargest() and nsmallest(): в модуле алгоритма очереди кучи heapq

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

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

>>> 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 Фреймворки

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

Простая программа Tkinter, представленная ниже, демонстрирует использование лямбды, назначенной команде кнопки 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 *:

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

Интерпритатор 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.

Другой подход — использовать лямбду:

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

Это решение чище, более читабельно и быстрее вводится в интерпретаторе.

Monkey Patching

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

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

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()

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

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

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

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

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 secretts.token_hex() перезаписывается лямбда-выражением, которое будет возвращать детерминированное значение feedfacecafebeef, позволяющее подтвердить правильность теста. monkeypatch позволяет вам контролировать область переопределения. В приведенном выше примере при вызове secretts.token_hex() в последующих тестах без использования monkey patching будет выполняться обычная реализация этой функции.

Выполнение теста 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() был выполнен, и результаты были ожидаемыми в контексте теста.

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

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

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

Map

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

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

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

>>> 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]

Reduce

Начиная с 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

Более идиоматический подход, использующий выражение генератора в качестве аргумента для 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 вместо оператора присваивания, который связывает лямбду непосредственно с идентификатором. (Источник)

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

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

Заключение

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

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

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

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

Оригинальная статья:  Andre Burgaud  How to Use Python lambda Functions

Была ли вам полезна эта статья?


синтаксис, аргументы и много примеров ~ PythonRu

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

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

Что такое лямбда в Python?

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

Для этого потребуется немного поменять направление мышление. Как вы знаете, все в Python является объектом.

Например, когда мы запускаем эту простейшую строку кода

x = 5

Создается объект Python типа int, который сохраняет значение 5. x же является символом, который ссылается на объект.

Теперь проверим тип x и адрес, на которой он ссылается. Это можно сделать с помощью встроенных функций type и id.

>>> type(x)
<class 'int'>
>>> id(x)
4308964832

В итоге x ссылается на объект типа int, а расположен он по адресу, который вернула функция id.

Просто и понятно.

А что происходит при определении вот такой функции:

>>> def f(x):
...     return x * x
...

Повторим упражнение и узнаем type и id объекта f.

>>> def f(x):
...     return x * x
...
>>> type(f)
<class 'function'>
>>> id(f)
4316798080

Оказывается, в Python есть класс function, а только что определенная функция f — это его экземпляр. Так же как x был экземпляром класса integer. Другими словами, о функциях можно думать как о переменных. Разница лишь в том, что переменные хранят данные, а функции — код.

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

Рассмотрим простой пример, где функция f передается другой функции.

def f(x):
    return x * x

def modify_list(L, fn):
    for idx, v in enumerate(L):
        L[idx] = fn(v)

L = [1, 3, 2]
modify_list(L, f)
print(L)


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

Итак, modify_list — это функция, которая принимает список L и функцию fn в качестве аргументов. Затем она перебирает список элемент за элементом и применяет функцию к каждому из них.

Это общий способ изменения объектов списка, ведь он позволяет передать функцию, которая займется преобразованием. Так, если передать modify_list функцию f, то результатом станет список, где все значения будут возведены в квадрат.

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

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

lambda arguments: expression

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

>>> f = lambda x: x * x
>>> type(f)
<class 'function'>

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

Лямбда с несколькими аргументами

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

>>> f = lambda x: x * x
>>> f(5)
25

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

>>> f = lambda x, y: x * y
>>> f(5, 2)
10

Отлично! А как насчет лямбда-функции без аргументов?

Лямбда-функция без аргументов

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

>>> f = lambda: True
>>> f()
True

Несколько лямбда-функций

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

Ответ однозначен: нет.

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

Примеры лямбда-функций

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

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

Распространенная операция со списками в Python — применение операции к каждому элементу.


map() — это встроенная функция Python, принимающая в качестве аргумента функцию и последовательность. Она работает так, что применяет переданную функцию к каждому элементу.

Предположим, есть список целых чисел, которые нужно возвести в квадрат с помощью map.

>>> L = [1, 2, 3, 4]
>>> list(map(lambda x: x**2, L))
[1, 4, 9, 16]

Обратите внимание на то, что в Python3 функция map возвращает объект Map, а в Python2 — список.

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

Вот еще один пример.

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

filter() — это еще одна встроенная функция, которая фильтрует последовательность итерируемого объекта.

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

Если возвращаемое значение — True, элемент остается. В противном случае — отклоняется. Определим, например, простую функцию, которая возвращает True для четных чисел и False — для нечетных:

def even_fn(x):
  if x % 2 == 0:
    return True
  return False

print(list(filter(even_fn, [1, 3, 2, 5, 20, 21])))


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

print(list(filter(lambda x: x % 2 == 0, [1, 3, 2, 5, 20, 21])))

И в этом сила лямбда-функций.

Лямбда-функция и сортировка списков

Сортировка списка — базовая операция в Python. Если речь идет о списке чисел или строк, то процесс максимально простой. Подойдут встроенные функции sort и sorted.

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

Функция применяется ко всем элементам объекта, а возвращаемое значение — то, на основе чего выполнится сортировка. Рассмотрим пример. Есть класс Employee.

class Employee:
    def __init__(self, name, age):
        self.name = name
        self.age = age

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

Alex = Employee('Alex', 20)
Amanda = Employee('Amanda', 30)
David = Employee('David', 15)
L = [Alex, Amanda, David]

Предположим, что мы хотим отсортировать его на основе поля age сотрудников. Вот что нужно сделать для этого:

L.sort(key=lambda x: x.age)
print([item.name for item in L])

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

Пара слов о выражениях и инструкциях

Как уже упоминалось, лямбда могут иметь только одно выражение (expression) в теле.


Обратите внимание, что речь идет не об инструкции (statement).

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

Например, инструкция if или циклы for и while являются примерами инструкций. Заменить инструкцию на значение попросту невозможно.

А вот выражения — это значения. Запросто можно заменить все выражения в программе на значения, и программа продолжит работать корректно.

Например:

  • 3 + 5 — выражение со значением 8
  • 10 > 5 — выражение со значением True
  • True and (5 < 3) — выражение со значением False

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

Проблема с лямбда-датчиком на BMW 7 серии

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

Что такое лямбда-функция на BMW 7 серии?

Итак, мы начинаем наше содержание с интерес лямбда-зонда на BMW 7 серии , мы сначала узнаем, какова задача этого зонда, а затем как он функционирует.

Роль лямбда-зонда на BMW 7 серии

Впервые созданный Volvo в 1970-х годах, он начал появляться на наших автомобилях в 1990-х годах с первыми стандартами выбросов EURO 1. Также называемый датчик кислорода на BMW 7 серии , его цель — регулировать количество кислорода в выхлопных газах, это позволит двигателю изменять топливно-воздушную смесь на уменьшить загрязнение, выбрасываемое автомобилем, и снизить расход топлива автомобилем.

Порядок действий лямбда-зонда на BMW 7 серии

Прежде чем описывать вам разные осложнения лямбда-зонда на BMW 7 серии , мы рассмотрим его функционирование более подробно, чтобы вы могли точно определить, как он функционирует, и, таким образом, более спокойно подумать об исправлении связанной с ним проблемы.
Как мы уже говорили, лямбда-зонд выполняет функцию контроль количества кислорода в выхлопных газах . Можно было бы наивно предположить, что эти данные измеряются перед камерой сгорания, но это наоборот. измеряется на выходе из двигателя . Со стандартом EURO 1 до катализатора требовался только один лямбда-зонд, но с появлением более жестких стандартов теперь 2 лямбда-зонда, один до и один после катализатора . Интересно получить более точные данные, скомпилировав два зонда. Эти данные отправляются в ЭБУ, который адаптирует количество воздуха и бензина, подаваемого в двигатель, для оптимизации сгорания. .

Проблема с лямбда-датчиком на BMW 7 серии

.
Наконец, теперь мы собираемся атаковать раздел, который, несомненно, больше всего интересует вас в этом контенте, как с этим справиться, если у вас есть проблема лямбда-датчика на BMW 7 серии . На первом этапе мы увидим как найти лямбда-зонд HS и на втором этапе, как его заменить.

Как узнать, является ли лямбда-зонд на BMW 7 серии HS

.
Важно знать, что лямбда-зонд, вообще говоря, срок службы 150 км , эти данные могут отличаться в зависимости от года выпуска вашего BMW 7 серии, вашего вождения и исправности вашего двигателя. Плохо ухаживаемый двигатель, который выделяет несгоревшие газы, может полностью изменить ваш лямбда-зонд. Одна из подсказок, которая может уведомить вас о неисправности лямбда-зонда на BMW 7 серии может загореться свет двигателя, если вы хотите выключите свет двигателя вашего BMW 7 серии, не стесняйтесь обращаться за советом к нашему специальному контенту, чтобы узнать, как действовать. Единственный эффективный способ убедиться, что вы у вас проблема с лямбда-датчиком на вашем BMW 7 серии и дополнить ваш автомобиль диагностическим случаем, для этого не стесняйтесь обращаться за советом к нашему руководству, которое объясняет вам BMW 7 серииКак прочитать код неисправности BMW 7 серии. Обратите внимание, что если у вас возникли проблемы с одним из ваших лямбда-датчиков, единственным средством их устранения будет замена неисправного датчика.

Как заменить лямбда-зонд на БМВ 7 серии?

И, наконец, сосредоточимся на исправление проблем с лямбда-зондом на BMW 7 серии , объясняя, как заменить лямбду Датчик.

Замену лямбда-зонда довольно легко выполнить, и у вас будет возможность сделать это самостоятельно, используя минимум инструментов и механических знаний. А лямбда-зонд стоит от 25 до 50 € , лучше заменить 2 лямбда-зонда перед и после каталитического нейтрализатора, потому что, если один из них неисправен, второй рискует быстро уронить вас. Для его замены потребуется поставь свой BMW 7 серии на свечи и на уровне вашего катализатора открутите зонды, отсоедините их, снова подсоедините и прикрутите новые . После повторного подключения у вас больше не должно возникнуть проблем с лямбда-датчиком на BMW 7 серии.

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

Проблема с лямбда-датчиком на Ford Expedition

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

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

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

Роль лямбда-зонда на Ford Expedition

Впервые произведенный Volvo в 1970-х годах, он начал появляться на наших автомобилях в 1990-х годах с первыми требованиями к выбросам EURO 1. Также называемый датчик кислорода на Ford Expedition , его задача — регулировать количество кислорода в выхлопных газах, это позволит двигателю изменять топливно-воздушную смесь таким образом, чтобы уменьшить загрязнение, выбрасываемое автомобилем, и снизить расход топлива автомобилем.

Порядок работы лямбда-зонда на Ford Expedition

Прежде чем объяснять вам разные проблемы лямбда-зонда на Ford Expedition , мы более подробно рассмотрим его функционирование, чтобы вы могли точно понять, как он функционирует, и, таким образом, более спокойно подумать об исправлении связанной с ним проблемы.
Как мы уже говорили, лямбда-зонд выполняет функцию управление количеством кислорода в выхлопных газах . Можно было бы наивно предположить, что эти данные определены до горения, но это наоборот. измеряется на выходе из двигателя . В стандарте EURO 1 до катализатора требовался только один лямбда-зонд, но с появлением более радикальных спецификаций теперь есть 2 лямбда-зонда, один до и один после катализатора . Преимущество состоит в том, чтобы получить более точные данные, скомпилировав два зонда. Эти данные отправляются в ЭБУ, который изменяет количество воздуха и бензина, подаваемого в двигатель, для улучшения сгорания. .

Проблема с лямбда-датчиком на Ford Expedition

.
Наконец, мы собираемся атаковать раздел, который вам, несомненно, больше всего нравится на этой странице контента, как с этим справиться, если у вас есть проблема лямбда-зонда на Ford Expedition . На первом этапе мы увидим как определить лямбда-зонд HS и на втором этапе, как это изменить.

Как узнать, является ли лямбда-зонд на Ford Expedition HS

.
Необходимо знать, что лямбда-зонд вообще срок службы 150 км , эта статистика может варьироваться в зависимости от года вашей экспедиции Ford, вашего вождения и исправности вашего двигателя. Плохой уход за двигателем, который выделяет несгоревшие газы, может навсегда повредить ваш лямбда-зонд. Одна из подсказок, которая может предупредить о неисправности лямбда-зонда на Ford Expedition может загореться свет двигателя, если вы хотите выключите свет двигателя вашего Ford Expedition, не думайте дважды, чтобы посетить нашу специальную страницу с контентом, чтобы увидеть, что делать дальше. Единственный эффективный метод, позволяющий убедиться, что вы у вас есть проблема с лямбда-датчиком на вашем Ford Expedition и довести свой автомобиль до диагностического случая, для этого не думайте дважды, чтобы обратиться за советом к нашему руководству, которое вам объяснит Ford Expeditionкак прочитать код неисправности Ford Expedition. Обратите внимание, что если у вас возникли проблемы с одним из ваших лямбда-датчиков, единственным решением для их решения будет замена неисправного датчика.

Как поменять лямбда-зонд на Ford Expedition?

И напоследок сосредоточимся на исправление проблем с лямбда-зондом на Ford Expedition , описывая, как заменить лямбду Датчик.

Заменить лямбда-зонд довольно просто, и у вас будет возможность сделать это самостоятельно, используя минимум инструментов и механических навыков. А лямбда-зонд стоит от 25 до 50 € , лучше заменить 2 лямбда-зонда перед и после каталитического нейтрализатора, потому что, если один из них неисправен, второй рискует быстро уронить вас. Для его замены потребуется поставь свой Ford Expedition на свечи и на уровне вашего катализатора открутите зонды, отсоедините их, снова подсоедините и прикрутите новые . После повторного подключения у вас больше не должно возникнуть проблем с лямбда-датчиком на Ford Expedition.

Если вам нужно больше руководств по Ford Expedition, перейдите на наш Форд Экспедишн категория.

Консоль Lambda — AWS Lambda

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

Приложения

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

Функции

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

Для вашего удобства в консоли есть редактор кода.

Подпись кода

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

Слои

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

Редактировать код с помощью редактора консоли

Вы можете использовать редактор кода в консоли AWS Lambda для написания, тестирования и просмотра результатов выполнения вашего Код лямбда-функции.Редактор кода поддерживает языки, не требующие компиляции, например Node.js и Python. Редактор кода поддерживает только пакеты развертывания архива .zip, и размер пакета развертывания должен быть менее 3 МБ.

Редактор кода включает строку меню , окон и панель редактора .

Список того, что делают команды, см. в разделе Команды меню. ссылка в Руководстве пользователя AWS Cloud9 .Обратите внимание, что некоторые из команд, перечисленных в этом ссылки недоступны в редакторе кода.

Работа с файлами и папками

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

Чтобы отобразить или скрыть окно среды , выберите Кнопка Окружающая среда . Если кнопка Environment не отображается, выберите Окно, Среда в строке меню.

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

Чтобы открыть несколько файлов и отобразить их содержимое в панели редактора , выберите файлы в окне Environment . Щелкните правой кнопкой мыши выделенный фрагмент и выберите Открыть .

Чтобы создать новый файл , выполните одно из следующих действий:

  • В окне Environment щелкните правой кнопкой мыши папку, в которую вы хотите поместить новый файл, а затем выберите Новый файл .Введите имя и расширение файла, а затем нажмите Введите .

  • Выберите File, New File в строке меню. Когда вы будете готовы сохранить файл, выберите Файл , Сохранить или Файл , Сохранить как в строке меню. Затем используйте Сохранить как Диалоговое окно, в котором можно указать имя файла и выбрать место для его сохранения.

  • На панели кнопок вкладок в панели редактора выберите кнопку + , а затем выберите Новый файл .Когда вы будете готовы сохранить файл, выберите File, Save или Файл, Сохранить как в строке меню. Затем используйте диалоговое окно Сохранить как . который отображается, чтобы назвать файл и выбрать, где его сохранить.

Чтобы создать новую папку , щелкните правой кнопкой мыши папку в Окно Environment , куда вы хотите поместить новую папку, а затем выберите New Папка .Введите имя папки, а затем нажмите Введите .

Чтобы сохранить файл , с открытым файлом и его содержимым, видимым в редакторе выберите File, Save в строке меню.

Чтобы переименовать файл или папку , щелкните правой кнопкой мыши файл или папку в Окно среды . Введите имя замены, а затем нажмите Введите .

Чтобы удалить файлы или папки , выберите файлы или папки в Окно среды .Щелкните правой кнопкой мыши выделение и выберите Удалить . Затем подтвердите удаление, выбрав Да (для одного выбора) или Да для Все .

Чтобы вырезать, копировать, вставлять или дублировать файлы или папки , выберите файлы или папки в окне Environment . Щелкните правой кнопкой мыши выделенный фрагмент и выберите Вырезать , Копировать , Вставить или Дубликат соответственно.

Чтобы свернуть папки , выберите значок шестеренки в Окно среды , а затем выберите Свернуть все папки .

Чтобы показать или скрыть скрытые файлы , выберите значок шестеренки в Окно среды , а затем выберите Показать скрытые файлы .

Работа с кодом

Используйте панель редактора в редакторе кода для просмотра и написания кода.

Работа с кнопками вкладок

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

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

Чтобы закрыть открытый файл , выполните одно из следующих действий:

  • Выберите значок X на вкладке файла.

  • Выберите вкладку файла. Затем выберите кнопку раскрывающегося меню на панели кнопок вкладок и выберите Закрыть панель .

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

Чтобы создать новый файл , нажмите кнопку + во вкладке панель кнопок, а затем выберите Новый файл .Когда вы будете готовы сохранить файл, выберите Файл , Сохранить или Файл , Сохранить как в строке меню. Затем используйте Сохранить как Диалоговое окно, в котором можно указать имя файла и выбрать место для его сохранения.

Работа со строкой состояния

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

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

Чтобы изменить цветовую схему кода в активном файле , выберите цвет кода селектор схемы, а затем выберите новую цветовую схему кода.

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

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

Работа в полноэкранном режиме

Вы можете расширить редактор кода, чтобы получить больше места для работы с вашим кодом.

Чтобы расширить редактор кода до краев окна веб-браузера, выберите переключатель Toggle полноэкранная кнопка в строке меню.

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

В полноэкранном режиме в строке меню отображаются дополнительные параметры: Сохранить и Тест .При выборе Save код функции сохраняется. Выбор Test или Configure Events позволяет создавать или редактировать тестовые мероприятия.

Работа с настройками

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

Чтобы изменить настройки редактора кода, выберите значок шестеренки Preferences в строке меню.

Список того, что делают настройки, см. в следующих ссылках в AWS Cloud9 User Руководство .

Обратите внимание, что некоторые параметры, перечисленные в этих справочниках, недоступны в редакторе кода.

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

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

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

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

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

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

История

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

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

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

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

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

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

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

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

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

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

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

>>>
  >>> определение идентификатора (x):
... вернуть х
  

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

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

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

  • Ключевое слово: лямбда
  • Связанная переменная: x
  • Корпус А: x

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

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

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

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

>>>
  >>> (лямбда х: х + 1)(2)
3
  

Сокращение — это стратегия лямбда-исчисления для вычисления значения выражения.В текущем примере он состоит из замены связанной переменной x аргументом 2 :

.
  (лямбда х: х + 1)(2) = лямбда 2: 2 + 1
                     = 2 + 1
                     = 3
  

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

>>>
  >>> add_one = лямбда х: х + 1
>>> add_one(2)
3
  

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

  по определению add_one(x):
    вернуть х + 1
  

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

>>>
  >>> full_name = lambda first, last: f'Полное имя: {first.title()} {last.title()}'
>>> full_name('guido', 'van rossum')
«Полное имя: Гвидо Ван Россум»
  

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

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

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

  • Анонимные функции
  • Лямбда-функции
  • Лямбда-выражения
  • Лямбда-абстракции
  • Лямбда форма
  • Функциональные литералы

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

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

>>>
  >>> лямбда x, y: x + y
  

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

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

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

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

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

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

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

>>>
  >>> (лямбда x, y: x + y)(2, 3)
5
  

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

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

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

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

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

>>>
  >>> high_ord_func = лямбда x, func: x + func(x)
>>> high_ord_func(2, лямбда х: х * х)
6
>>> high_ord_func(2, лямбда х: х + 3)
7
  

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

Python Lambda и обычные функции

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

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

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

Функции

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

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

>>>
  >>> импорт
>>> добавить = лямбда х, у: х + у
>>> введите (добавить)
<класс 'функция'>
>>> дис.дис(добавить)
  1 0 LOAD_FAST 0 (х)
              2 LOAD_FAST 1 (г)
              4 BINARY_ADD
              6 ВОЗВРАТ_ЗНАЧЕНИЕ
>>> добавить
<функция <лямбда> по адресу 0x7f30c6ce9ea0>
  

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

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

>>>
  >>> импорт
>>> def add(x, y): вернуть x + y
>>> введите (добавить)
<класс 'функция'>
>>> дис.дис(добавить)
  1 0 LOAD_FAST 0 (х)
              2 LOAD_FAST 1 (г)
              4 BINARY_ADD
              6 ВОЗВРАТ_ЗНАЧЕНИЕ
>>> добавить
<функция добавляется по адресу 0x7f30c6ce9f28>
  

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

Отслеживание

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

. >>>
  >>> div_zero = лямбда х: х / 0
>>> div_zero(2)
Traceback (последний последний вызов):
    Файл "", строка 1, в 
    Файл "", строка 1, в 
ZeroDivisionError: деление на ноль
  

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

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

>>>
  >>> def div_zero(x): вернуть x / 0
>>> div_zero(2)
Traceback (последний последний вызов):
    Файл "", строка 1, в 
    Файл "", строка 1, в div_zero
ZeroDivisionError: деление на ноль
  

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

Синтаксис

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

  • Он может содержать только выражения и не может включать операторы в свое тело.
  • Записывается как однострочное исполнение.
  • Не поддерживает аннотации типов.
  • Его можно вызвать немедленно (IIFE).

Без заявлений

Лямбда-функция не может содержать операторов.SyntaxError: неверный синтаксис

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

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

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

>>>
  >>> (лямбда х:
... (x % 2 и «нечетное» или «четное»)))(3)
'странный'
  

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

Типовые обозначения

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

  def full_name(first: str, last: str) -> str:
    вернуть f'{first.title()} {last.title()}'
  

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

>>>
  >>> лямбда первая: ул, последняя: ул: первая.title() + " " + last.title() -> ул
  Файл "", строка 1
    лямбда first: str, last: str: first.title() + " " + last.title() -> str

SyntaxError: неверный синтаксис
  

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

IIFE

Вы уже видели несколько примеров немедленного выполнения функции:

>>>
  >>> (лямбда x: x * x)(3)
9
  

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

Аргументы

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

  • Позиционные аргументы
  • Именованные аргументы (иногда называемые ключевыми аргументами)
  • Переменный список аргументов (часто называемый varargs )
  • Переменный список аргументов ключевого слова
  • Аргументы, содержащие только ключевое слово

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

>>>
  >>> (лямбда x, y, z: x + y + z)(1, 2, 3)
6
>>> (лямбда x, y, z=3: x + y + z)(1, 2)
6
>>> (лямбда x, y, z=3: x + y + z)(1, y=2)
6
>>> (лямбда *аргументы: сумма(аргументы))(1,2,3)
6
>>> (лямбда **kwargs: сумма(kwargs.значения()))(один=1, два=2, три=3)
6
>>> (лямбда x, *, y=0, z=0: x + y + z)(1, y=2, z=3)
6
  

Декораторы

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

  def some_decorator(f):
    def обертывания (*аргументы):
        print(f"Вызов функции '{f.__name__}'")
        вернуть f (аргументы)
    возврат обертывания

@some_decorator
деф украшенная_функция (х):
    print(f"С аргументом '{x}'")
  

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

  Вызов функции 'decorated_function'
С аргументом 'Python'
  

decorated_function() печатает только С аргументом 'Python' , но декоратор добавляет дополнительное поведение, которое также печатает Вызов функции 'decorated_function' .

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

  1# Определение декоратора
 2def трассировка (f):
 3 def wrap(*args, **kwargs):
 4 print(f"[TRACE] func: {f.__name__}, args: {args}, kwargs: {kwargs}")
 5 вернуть f(*args, **kwargs)
 6
 7 обратная обмотка
 8
 9# Применение декоратора к функции
[email protected]трассировка
11def add_two(x):
12 возврат х + 2
13
14# Вызов украшенной функции
15add_two(3)
16
17# Применение декоратора к лямбде
18print((трассировка(лямбда х: х ** 2))(3))
  

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

  [TRACE] функция: add_two, аргументы: (3,), kwargs: {}
[TRACE] func: <лямбда>, аргументы: (3,), kwargs: {}
9
  

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

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

  список (карта (трассировка (лямбда x: x * 2), диапазон (3)))
  

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

  [TRACE] Вызов  с аргументами (0,) и kwargs {}
[TRACE] Вызов  с аргументами (1,) и kwargs {}
[TRACE] Вызов  с аргументами (2,) и kwargs {}
[0, 2, 4]
  

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

Вы увидите map() более подробно в Map.

Лямбда также может быть декоратором, но это не рекомендуется. Если вам нужно это сделать, обратитесь к PEP 8, Рекомендации по программированию.

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

Закрытие

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

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

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

  1def external_func(x):
 2 у = 4
 3 определение внутренней_функции (z):
 4 print(f"x = {x}, y = {y}, z = {z}")
 5 вернуть х + у + г
 6 вернуть внутреннюю_функцию
 7
 8 для i в диапазоне (3):
 9 замыкание = external_func(i)
10 print(f"замыкание({i+5}) = {замыкание(i+5)}")
  

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

  • x передается в качестве аргумента external_func() .
  • y является переменной, локальной для external_func() .
  • z — это аргумент, передаваемый в inner_func() .

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

  х = 0, у = 4, г = 5
закрытие (5) = 9
х = 1, у = 4, г = 6
закрытие (6) = 11
х = 2, у = 4, г = 7
закрытие (7) = 13
  

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

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

.
  1def external_func(x):
 2 у = 4
 3 вернуть лямбда z: x + y + z
 4
 5 для я в диапазоне (3):
 6 замыкание = external_func(i)
 7 print(f"замыкание({i+5}) = {замыкание(i+5)}")
  

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

  закрытие(5) = 9
закрытие (6) = 11
закрытие (7) = 13
  

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

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

Время оценки

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

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

>>>
  1>>> def wrap(n):
 2... защита f():
 3... напечатать(н)
 4... возврат f
 5...
 6>>> цифры = «один», «два», «три»
 7>>> функции = []
 8>>> для n цифрами:
 9... функц.добавить (обернуть (n))
10...
11>>> для f в функциях:
12... ф()
13...
14один
15два
16три
  

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

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

>>>
  1>>> числа = 'один', 'два', 'три'
 2>>> функции = []
 3>>> для n цифрами:
 4... funcs.append (лямбда: печать (n))
 5...
 6>>> для f в функциях:
 7... ф()
 8...
 9три
10три
11три
  

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

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

>>>
  1>>> числа = 'один', 'два', 'три'
 2>>> функции = []
 3>>> для n цифрами:
 4... funcs.append(лямбда n=n: print(n))
 5...
 6>>> для f в функциях:
 7... ф()
 8...
 9один
10два
11три
  

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

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

Тестирование лямбда-выражений

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

модульный тест

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

  импорт юниттест

addtwo = лямбда х: х + 2

класс LambdaTest(unittest.Прецедент):
    защита test_add_two (я):
        self.assertEqual (добавить два (2), 4)

    защита test_add_two_point_two (я):
        self.assertEqual (добавить два (2.2), 4.2)

    защита test_add_three (я):
        # Должен потерпеть неудачу
        self.assertEqual (добавить два (3), 6)

если __name__ == '__main__':
    unittest.main (многословие = 2)
  

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

  $ Python lambda_unittest.py
test_add_three (__main__.LambdaTest) ... FAIL
test_add_two (__main__.LambdaTest) ... ок
test_add_two_point_two (__main__.LambdaTest) ... ок

================================================== =====================
НЕУДАЧА: test_add_three (__main__.LambdaTest)
-------------------------------------------------- --------------------
Traceback (последний последний вызов):
  Файл «лямбда_unittest.ру", строка 18, в test_add_three
    self.assertEqual (добавить два (3), 6)
УтверждениеОшибка: 5!= 6

-------------------------------------------------- --------------------
Провел 3 теста за 0,001 с.

СБОЙ (сбои=1)
  

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

доктест

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

  addtwo = лямбда х: х + 2
addtwo.__doc__ = """Добавьте 2 к числу.
    >>> добавить два(2)
    4
    >>> добавитьдва(2.2)
    4.2
    >>> addtwo(3) # Должен потерпеть неудачу
    6
    """

если __name__ == '__main__':
    импортировать доктест
    doctest.testmod (подробный = Истина)
  

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

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

  $ Python lambda_doctest.py
Пытающийся:
    добавить два (2)
Ожидание:
    4
в порядке
Пытающийся:
    добавить два (2.2)
Ожидание:
    4.2
в порядке
Пытающийся:
    addtwo(3) # Должен потерпеть неудачу
Ожидание:
    6
******************************************************* ********************
Файл «lambda_doctest.py», строка 16, в __main__.addtwo
Неудачный пример:
    addtwo(3) # Должен потерпеть неудачу
Ожидал:
    6
Есть:
    5
1 товар не тестировался:
    __главный__
******************************************************* ********************
1 элемент имел сбои:
   1 из 3 в __main__.addtwo
3 теста по 2 пунктам.
2 прошли и 1 не прошел.
***Тест не пройден*** 1 сбой. 

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

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

Подробный обзор модульного тестирования в Python можно найти в статье Начало работы с тестированием в Python.

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

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

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

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

  • Не соответствует руководству по стилю Python (PEP 8)
  • Это громоздко и трудно читать.
  • Это излишне умно за счет трудной читабельности.

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

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

>>>
  >>> def throw(ex): поднять ex
>>> (лямбда: throw(Exception('Произошло что-то плохое')))()
Traceback (последний последний вызов):
    Файл "", строка 1, в 
    Файл "", строка 1, в 
    Файл "", строка 1, в броске
Исключение: случилось что-то плохое
  

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

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

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

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

>>>
  >>> (лямбда _: список(карта(лямбда _: _ // 2, _)))([1,2,3,4,5,6,7,8,9,10] )
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]
  

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

. >>>
  >>> (лямбда some_list: list(map(лямбда n: n // 2,
                                некоторый_список)))([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 = лямбда n: n // 2
      карта возврата (div_by_two, some_list)
>>> список(div_items([1,2,3,4,5,6,7,8,9,10])))
[0, 1, 1, 2, 2, 3, 3, 4, 4, 5]
  

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

Классы Python

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

  класс Автомобиль:
    """Автомобиль с методами в виде лямбда-функций."""
    def __init__(я, бренд, год):
        селф.бренд = бренд
        сам.год = год

    бренд = свойство (лямбда-я: getattr (я, '_brand'),
                     лямбда self, значение: setattr(self, '_brand', значение))

    год = свойство (лямбда-я: getattr (я, '_год'),
                    лямбда self, значение: setattr(self, '_year', значение))

    __str__ = лямбда self: f'{self.марка} {self.year}' # 1: ошибка E731

    honk = lambda self: print('Гудок!') # 2: ошибка E731
  

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

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

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

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

  по определению __str__(я):
    вернуть f'{self.brand} {self.year}'
  

марка будет записана так:

  @property
Def бренд (я):
    вернуть self._brand

@brand.setter
Def бренд (я, значение):
    self._brand = значение
  

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

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

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

  • Проблемы с читаемостью
  • Навязывание функционального образа мышления
  • Тяжелый синтаксис с ключевым словом lambda

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

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

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

Функции

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

>>>
  >>> список(карта(лямбда х: х.верхний(), ['кошка', 'собака', 'корова']))
['КОШКА', 'СОБАКА', 'КОРОВА']
>>> список(фильтр(лямбда x: 'o' в x, ['кошка', 'собака', 'корова']))
['собака', 'корова']
>>> из functools импортировать уменьшить
>>> уменьшить (лямбда-акк, х: f'{акк} | {х}', ['кошка', 'собака', 'корова'])
кот | собака | корова'
  

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

Основные функции

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

  • sort() : метод списка
  • sorted() , min() , max() : встроенные функции
  • nlargest() и nsmallest() : в модуле алгоритма очереди кучи heapq

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

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

>>>
  >>> ids = ['id1', 'id2', 'id30', 'id3', 'id22', 'id100']
>>> print(sorted(id)) # Лексикографическая сортировка
['id1', 'id100', 'id2', 'id22', 'id3', 'id30']
>>> sorted_ids = sorted(ids, key=lambda x: int(x[2:])) # Целочисленная сортировка
>>> печать (отсортированные_идентификаторы)
['id1', 'id2', 'id3', 'id22', 'id30', 'id100']
  

Платформы пользовательского интерфейса

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

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

  импортировать tkinter как tk
импорт системы

окно = tk.Tk()
window.grid_columnconfigure(0, вес=1)
окно.название("лямбда")
окно.геометрия("300x100")
label = tk.Label(window, text="Лямбда-исчисление")
label.grid (столбец = 0, строка = 0)
кнопка = т.к.Кнопка(
    окно,
    текст = "Обратный",
    команда = лямбда: метка.configure (текст = метка.cget («текст») [:: -1]),
)
button.grid (столбец = 0, строка = 1)
окно.mainloop()
  

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

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

Чтобы изучить wxPython, ознакомьтесь со статьей Как создать приложение Python с графическим интерфейсом с помощью wxPython.

Интерпретатор Python

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

время

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

>>>
  >>> from timeit импортировать timeit
>>> timeit("факториал(999)", "из математического импорта факториала", число=10)
0.0013087529951008037
  

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

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

. >>>
  >>> из факториала импорта математики
>>> timeit(лямбда: факториал(999), число=10)
0.0012704220062005334
  

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

Заплатка для обезьяны

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

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

  из контекстного менеджера импорта contextlib
секреты импорта

определение gen_token():
    """Создать случайный токен."""
    вернуть f'TOKEN_{secrets.token_hex(8)}'

@contextmanager
деф mock_token():
    """Менеджер контекста для обезьяньего исправления секретов.token_hex
    функционировать во время тестирования.
    """
    default_token_hex = секреты.token_hex
    secrets.token_hex = лямбда _: 'feedfacecafebeef'
    урожай
    secrets.token_hex = default_token_hex

защита test_gen_key():
    """Проверить случайный токен."""
    с mock_token():
        утверждать gen_token() == f"TOKEN_{'feedfacecafebeef'}"

test_gen_key()
  

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

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

Платформы модульного тестирования

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

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

  импортные секреты

определение gen_token():
    вернуть f'TOKEN_{secrets.token_hex(8)}'

защита test_gen_key (monkeypatch):
    monkeypatch.setattr('secrets.token_hex', лямбда _: 'feedfacecafebeef')
    утверждать gen_token() == f"TOKEN_{'feedfacecafebeef'}"
  

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

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

  $ pytest test_token.ру-в
============================= начинается тестовая сессия =================== ============
платформа Linux -- Python 3.7.2, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
каталог кеша: .pytest_cache
корневой каталог: /home/andre/AB/tools/bpython, inifile:
собрал 1 шт.

test_token.py::test_gen_key ПРОШЕЛ [100%]

=========================== 1 прошло за 0,01 секунды ================== =========
  

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

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

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

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

Чтобы узнать больше о генераторах списков, ознакомьтесь с разделом Когда использовать генератор списков в Python.Чтобы узнать больше о выражениях генератора, ознакомьтесь с разделом Как использовать генераторы и доходность в Python.

Карта

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

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

>>>
  >>> list(map(lambda x: x.capitalize(), ['кошка', 'собака', 'корова']))
['Кошка', 'Собака', 'Корова']
  

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

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

>>>
  >>> [х.капитализировать () для x в ['кошка', 'собака', 'корова']]
['Кошка', 'Собака', 'Корова']
  

Фильтр

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

. >>>
  >>> даже = лямбда х: х%2 == 0
>>> список(фильтр(четный, диапазон(11)))
[0, 2, 4, 6, 8, 10]
  

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

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

>>>
  >>> [x вместо x в диапазоне (11), если x%2 == 0]
[0, 2, 4, 6, 8, 10]
  

Уменьшить

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

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

>>>
  >>> импортировать functools
>>> пары = [(1, 'a'), (2, 'b'), (3, 'c')]
>>> functools.reduce(лямбда-акк, пара: акк + пара[0], пары, 0)
6
  

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

>>>
  >>> пары = [(1, 'а'), (2, 'б'), (3, 'с')]
>>> sum(x[0] для x в парах)
6
  

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

>>>
  >>> пары = [(1, 'а'), (2, 'б'), (3, 'с')]
>>> sum(x вместо x, _ попарно)
6
  

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

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

Являются ли Lambdas Pythonic или нет?

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

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

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

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

Помимо узкой области Python lambda , Как написать красивый код Python с помощью PEP 8 — это отличный ресурс, который вы можете проверить в отношении стиля кода в Python.

Заключение

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

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

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

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