""" Урок 42. Lambda-функции. Функции высшего порядка — весь код примеров одним файлом. Источник: subjects/python-fundamentals/course/lessons/42-lambda-higher-order-functions/examples.html Файл собран автоматически (tools/build_lesson_examples.py): правьте страницу урока. Запуск: python lesson-42.py """ # ==================================================================== # Пример 1. Функция — это объект: ссылка против вызова # ==================================================================== def square(x): return x * x f = square # ссылка: функция НЕ вызывалась print(square) # print(type(square)) # print(f(5)) # 25 — вызываем через другое имя print(square(5)) # 25 — здесь скобки, значит уже число # ==================================================================== # Пример 2. Словарь функций вместо цепочки if # ==================================================================== def add(a, b): return a + b def multiply(a, b): return a * b def subtract(a, b): return a - b operations = {"+": add, "*": multiply, "-": subtract} print(operations["+"](10, 5)) # 15 print(operations["*"](10, 5)) # 50 # безопасный выбор: неизвестный ключ не роняет программу operation = operations.get("/") if operation is None: print("Операция '/' не поддерживается") # по словарю функций можно пройти циклом for symbol, func in operations.items(): print(symbol, func(12, 4), func.__name__) # + 16 add # * 48 multiply # - 8 subtract # ==================================================================== # Пример 3. Функция высшего порядка: принимает функцию и возвращает функцию # ==================================================================== # 1) принимает функцию def apply(func, x): return func(x) print(apply(square, 5)) # 25 print(apply(len, "hello")) # 5 — встроенная функция тоже объект # 2) возвращает функцию (фабрика функций) def make_multiplier(n): def multiplier(x): return x * n return multiplier double = make_multiplier(2) triple = make_multiplier(3) print(double(10), triple(10)) # 20 30 print(double.__name__) # multiplier # ==================================================================== # Пример 4. lambda и def — одно и то же, разный синтаксис # ==================================================================== def f1(x): return x * x f2 = lambda x: x * x # так делать не надо: PEP 8 против именованных lambda print(f1(6), f2(6)) # 36 36 print((lambda x: x * x)(6)) # 36 — вызов на месте, без имени # уместное применение: короткий callback в аргументе words = ["banana", "kiwi", "apple"] print(sorted(words, key=lambda w: len(w))) # ['kiwi', 'apple', 'banana'] print(sorted(words, key=len)) # то же самое, но короче # ==================================================================== # Пример 5. map: одна и несколько последовательностей, ленивость # ==================================================================== from typing import Iterator nums = [1, 2, 3, 4, 5, 6, 7, 8] map_object = map(str, nums) print(map_object) # print(isinstance(map_object, Iterator)) # True print(*map_object) # 1 2 3 4 5 6 7 8 print(*map_object) # (пусто!) итератор уже исчерпан # map умеет работать с несколькими последовательностями сразу print(list(map(lambda x, y: x + y, nums, nums))) # [2, 4, 6, ..., 16] print(list(map(lambda a, b: a * b, [1, 2, 3], [10, 20]))) # [10, 40] # частый практический случай: смена типа перед вычислением prices = ["10.5", "3", "7.25"] print(sum(map(float, prices))) # 20.75 # ==================================================================== # Пример 6. filter: по условию и по «истинности» # ==================================================================== nums = [1, 2, 3, 4, 5, 6, 7, 8] print(list(filter(lambda x: x % 2 == 0, nums))) # [2, 4, 6, 8] # filter(None, ...) — быстрый способ выкинуть пустышки values = [0, 1, "", "text", None, [], [1], 0.0, "0"] print(list(filter(None, values))) # [1, 'text', [1], '0'] words = ["apple", "", "kiwi", None, "banana"] print(list(filter(None, words))) # ['apple', 'kiwi', 'banana'] # ==================================================================== # Пример 7. reduce: аккумулятор, initial и пустая коллекция # ==================================================================== from functools import reduce nums = [1, 2, 3, 4, 5, 6, 7, 8] print(reduce(lambda acc, x: acc + x, nums)) # 36 print(reduce(lambda acc, x: acc + x, nums, 10)) # 46 — начали с 10 print(reduce(lambda acc, x: acc + x, [10, 0, 0])) # 10 # пустая коллекция без initial — ошибка try: reduce(lambda acc, x: acc + x, []) except TypeError as error: print("TypeError:", error) # reduce() of empty iterable with no initial value print(reduce(lambda acc, x: acc + x, [], 0)) # 0 — с initial всё в порядке # ==================================================================== # Пример 7. reduce: аккумулятор, initial и пустая коллекция # ==================================================================== chunks = [["a", "b"], ["c"], ["d", "e"]] print(reduce(lambda acc, x: acc + x, chunks, [])) # ['a', 'b', 'c', 'd', 'e'] longest = reduce(lambda acc, w: acc if len(acc) >= len(w) else w, ["kiwi", "banana", "fig"]) print(longest) # banana # ==================================================================== # Пример 8. key=: сортировка по одному и по нескольким критериям # ==================================================================== d = {"c": 5, "b": 1, "a": 3} print(dict(sorted(d.items(), key=lambda item: item[0]))) # {'a': 3, 'b': 1, 'c': 5} — по ключу, по возрастанию print(dict(sorted(d.items(), key=lambda item: -item[1]))) # {'c': 5, 'a': 3, 'b': 1} — по значению, по убыванию print(dict(sorted(d.items(), key=lambda item: item[1], reverse=True))) # {'c': 5, 'a': 3, 'b': 1} — то же самое через reverse # ==================================================================== # Пример 8. key=: сортировка по одному и по нескольким критериям # ==================================================================== data = [("apple", 5), ("banana", 2), ("apple", 8), ("banana", 7), ("cherry", 1)] print(sorted(data, key=lambda x: (x[0], -x[1]))) # [('apple', 8), ('apple', 5), ('banana', 7), ('banana', 2), ('cherry', 1)] # название по алфавиту, внутри одинаковых названий — количество по убыванию people = [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 20}, {"name": "Carol", "age": 25}] print([p["name"] for p in sorted(people, key=lambda p: (-p["age"], p["name"]))]) # ['Alice', 'Carol', 'Bob'] — сначала старшие, ровесники по алфавиту # ==================================================================== # Пример 9. min, max, sum, any, all — свёртка без reduce # ==================================================================== people = [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 20}, {"name": "Carol", "age": 25}] print(max(people, key=lambda p: p["age"])) # {'name': 'Alice', 'age': 25} print(sum(p["age"] for p in people)) # 70 print(any(p["age"] > 24 for p in people)) # True — хотя бы один print(all(p["age"] > 24 for p in people)) # False — не все print(all([]), any([])) # True False — пустая коллекция # ==================================================================== # Пример 9. min, max, sum, any, all — свёртка без reduce # ==================================================================== def probe(n): print(" проверяю", n) return n > 3 print(any(probe(n) for n in [1, 5, 2, 7])) # проверяю 1 # проверяю 5 # True — элементы 2 и 7 даже не проверялись # ==================================================================== # Пример 10. Когда lambda заменяется готовым инструментом # ==================================================================== from functools import partial from operator import itemgetter data = [("apple", 5), ("banana", 2), ("apple", 8), ("banana", 7), ("cherry", 1)] print(sorted(data, key=itemgetter(1))[:2]) # [('cherry', 1), ('banana', 2)] print(list(map(itemgetter(0), data))[:3]) # ['apple', 'banana', 'apple'] # itemgetter(1) делает то же, что lambda x: x[1] def power(base, exponent): return base ** exponent cube = partial(power, exponent=3) # зафиксировали один аргумент print(cube(2), cube(5)) # 8 125 print(list(map(partial(round, ndigits=2), [3.14159, 2.71828]))) # [3.14, 2.72] # ==================================================================== # Пример 11. Конвейер обработки данных и его pythonic-альтернатива # ==================================================================== from functools import reduce orders = [ {"id": 1, "customer": "Alice", "total": 120.0, "paid": True}, {"id": 2, "customer": "Bob", "total": 45.5, "paid": False}, {"id": 3, "customer": "Carol", "total": 300.0, "paid": True}, {"id": 4, "customer": "Dave", "total": 80.0, "paid": True}, ] paid = list(filter(lambda order: order["paid"], orders)) # отобрали totals = list(map(lambda order: order["total"], paid)) # преобразовали revenue = reduce(lambda acc, value: acc + value, totals, 0.0) # свернули print(len(paid), totals, revenue) # 3 [120.0, 300.0, 80.0] 500.0 top = sorted(paid, key=lambda order: -order["total"])[:2] print([order["customer"] for order in top]) # ['Carol', 'Alice'] print(max(orders, key=lambda order: order["total"])["customer"]) # Carol # ==================================================================== # Пример 11. Конвейер обработки данных и его pythonic-альтернатива # ==================================================================== revenue = sum(order["total"] for order in orders if order["paid"]) print(revenue) # 500.0 # ==================================================================== # Пример 12. Ловушка: lambda в цикле помнит переменную, а не значение # ==================================================================== funcs = [lambda: i for i in range(3)] print([f() for f in funcs]) # [2, 2, 2] — а ожидали [0, 1, 2] # способ 1: зафиксировать значение аргументом по умолчанию funcs_fixed = [lambda i=i: i for i in range(3)] print([f() for f in funcs_fixed]) # [0, 1, 2] # способ 2: фабрика функций — каждый вызов создаёт свою область имён def make_multiplier(n): def multiplier(x): return x * n return multiplier factories = [make_multiplier(n) for n in (2, 3)] print([f(10) for f in factories]) # [20, 30]