Python -- мультипарадигменный язык с хорошей поддержкой функционального программирования. Разберём анонимные функции, функции высшего порядка и инструменты из functools и itertools.
Lambda-функции
Lambda -- анонимная функция в одно выражение:
# Basic lambda
square = lambda x: x ** 2
print(square(5)) # 25
# Multiple arguments
add = lambda a, b: a + b
print(add(3, 4)) # 7
# With default arguments
greet = lambda name, greeting="Привет": f"{greeting}, {name}!"
print(greet("Иван")) # Привет, Иван!
print(greet("Мария", "Здравствуй")) # Здравствуй, Мария!
# Lambda as key function (most common use case)
names = ["Иван", "Мария", "Александр", "Пётр", "Ли"]
print(sorted(names, key=lambda n: len(n)))
# ['Ли', 'Иван', 'Пётр', 'Мария', 'Александр']
# Sort by multiple criteria
students = [("Иван", 85), ("Мария", 92), ("Пётр", 85)]
print(sorted(students, key=lambda s: (-s[1], s[0])))
# [('Мария', 92), ('Иван', 85), ('Пётр', 85)]
# Lambda with conditional expression
classify = lambda x: "положительное" if x > 0 else "отрицательное" if x < 0 else "ноль"
print(classify(5)) # положительное
print(classify(-3)) # отрицательное
print(classify(0)) # ноль
Когда использовать lambda
# GOOD: short, one-time use as key/callback
sorted(data, key=lambda x: x["name"])
button.on_click(lambda: print("Clicked"))
max(users, key=lambda u: u.age)
# BAD: complex logic, reuse needed, hard to read
# Don't do this:
process = lambda x: (x.strip().lower().replace(" ", "_") if x else "default")
# Instead, define a named function:
def normalize_name(name: str) -> str:
"""Normalize a name for use as an identifier."""
if not name:
return "default"
return name.strip().lower().replace(" ", "_")
# BAD: assigning lambda to a variable (PEP 8 violation)
# square = lambda x: x ** 2 # Use def instead
# GOOD: inline in an expression
print(sorted([3, 1, 4], key=lambda x: -x)) # [4, 3, 1]
map(), filter(), reduce()
map()
# map(func, iterable) - apply function to each element
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x**2, numbers))
print(squares) # [1, 4, 9, 16, 25]
# map with built-in functions
strings = ["1", "2", "3", "4"]
ints = list(map(int, strings))
print(ints) # [1, 2, 3, 4]
# map with multiple iterables
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))
print(sums) # [11, 22, 33]
# Usually, list comprehension is more Pythonic
squares = [x**2 for x in numbers] # preferred over map()
filter()
# filter(func, iterable) - keep elements where func returns True
numbers = range(20)
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Filter with None removes falsy values
data = [0, 1, "", "hello", None, False, True, [], [1, 2]]
truthy = list(filter(None, data))
print(truthy) # [1, 'hello', True, [1, 2]]
# List comprehension equivalent (preferred)
evens = [x for x in range(20) if x % 2 == 0]
reduce()
from functools import reduce
# reduce(func, iterable, initial) - accumulate result
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, numbers, 0)
print(total) # 15
# Product of all numbers
product = reduce(lambda acc, x: acc * x, numbers, 1)
print(product) # 120
# Find maximum (reduce is rarely the best choice)
maximum = reduce(lambda a, b: a if a > b else b, numbers)
print(maximum) # 5
# Better: max(numbers)
# Flatten nested lists
nested = [[1, 2], [3, 4], [5, 6]]
flat = reduce(lambda acc, lst: acc + lst, nested, [])
print(flat) # [1, 2, 3, 4, 5, 6]
# Better: [x for sublist in nested for x in sublist]
# Build a pipeline
from functools import reduce
def pipeline(*functions):
"""Compose functions into a pipeline."""
def execute(data):
return reduce(lambda result, func: func(result), functions, data)
return execute
process = pipeline(
str.strip,
str.lower,
lambda s: s.replace(" ", "_"),
)
print(process(" Hello World ")) # 'hello_world'
functools: partial и другие инструменты
functools.partial
from functools import partial
# Freeze some arguments of a function
def power(base: int, exponent: int) -> int:
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(5)) # 125
# Practical: configure HTTP requests
import urllib.request
def fetch(url: str, timeout: int = 30, headers: dict | None = None) -> str:
"""Simplified fetch function."""
return f"GET {url} timeout={timeout}"
# Create specialized fetchers
fast_fetch = partial(fetch, timeout=5)
api_fetch = partial(fetch, headers={"Authorization": "Bearer token123"})
print(fast_fetch("https://api.example.com"))
# GET https://api.example.com timeout=5
# partial vs lambda
# These are equivalent:
add_ten = partial(lambda a, b: a + b, 10)
add_ten_lambda = lambda b: 10 + b # noqa
# partial preserves function metadata better
print(add_ten.func) # <lambda>
print(add_ten.args) # (10,)
print(add_ten.keywords) # {}
functools.reduce и accumulate
from functools import reduce
from itertools import accumulate
numbers = [1, 2, 3, 4, 5]
# reduce gives final result only
total = reduce(lambda a, b: a + b, numbers)
print(total) # 15
# accumulate gives intermediate results (lazy)
running_sum = list(accumulate(numbers))
print(running_sum) # [1, 3, 6, 10, 15]
# Custom accumulator
import operator
running_product = list(accumulate(numbers, operator.mul))
print(running_product) # [1, 2, 6, 24, 120]
# Running maximum
data = [3, 1, 4, 1, 5, 9, 2, 6]
running_max = list(accumulate(data, max))
print(running_max) # [3, 3, 4, 4, 5, 9, 9, 9]
Модуль operator
Модуль operator предоставляет функции для стандартных операторов:
import operator
# Arithmetic
print(operator.add(2, 3)) # 5
print(operator.mul(4, 5)) # 20
print(operator.pow(2, 10)) # 1024
# Comparison
print(operator.lt(3, 5)) # True (3 < 5)
print(operator.eq("a", "a")) # True
# Item access
getter = operator.itemgetter(1)
print(getter([10, 20, 30])) # 20
# Multiple items
multi_getter = operator.itemgetter(0, 2)
print(multi_getter([10, 20, 30])) # (10, 30)
# Practical: sort by dictionary key
users = [
{"name": "Иван", "age": 25},
{"name": "Мария", "age": 30},
{"name": "Пётр", "age": 22},
]
by_age = sorted(users, key=operator.itemgetter("age"))
print([u["name"] for u in by_age]) # ['Пётр', 'Иван', 'Мария']
# attrgetter for object attributes
from dataclasses import dataclass
@dataclass
class Student:
name: str
grade: int
students = [Student("Иван", 85), Student("Мария", 92), Student("Пётр", 78)]
by_grade = sorted(students, key=operator.attrgetter("grade"), reverse=True)
print([s.name for s in by_grade]) # ['Мария', 'Иван', 'Пётр']
# methodcaller
names = ["иван", "мария", "пётр"]
upper_names = list(map(operator.methodcaller("upper"), names))
print(upper_names) # ['ИВАН', 'МАРИЯ', 'ПЁТР']
itertools -- инструменты для итерации
from itertools import (
chain, islice, cycle, repeat,
takewhile, dropwhile,
product, permutations, combinations,
groupby, starmap, zip_longest
)
# chain - combine iterables
combined = list(chain([1, 2], [3, 4], [5, 6]))
print(combined) # [1, 2, 3, 4, 5, 6]
# chain.from_iterable - flatten one level
matrix = [[1, 2], [3, 4], [5, 6]]
flat = list(chain.from_iterable(matrix))
print(flat) # [1, 2, 3, 4, 5, 6]
# islice - lazy slicing of any iterable
from itertools import count
first_5_squares = list(islice((x**2 for x in count(1)), 5))
print(first_5_squares) # [1, 4, 9, 16, 25]
# cycle - infinite repetition
from itertools import cycle
colors = cycle(["red", "green", "blue"])
print([next(colors) for _ in range(7)])
# ['red', 'green', 'blue', 'red', 'green', 'blue', 'red']
# takewhile / dropwhile
data = [1, 3, 5, 7, 2, 4, 6, 8]
before_even = list(takewhile(lambda x: x % 2 != 0, data))
print(before_even) # [1, 3, 5, 7]
after_odd = list(dropwhile(lambda x: x % 2 != 0, data))
print(after_odd) # [2, 4, 6, 8]
# product - Cartesian product
sizes = ["S", "M", "L"]
colors = ["red", "blue"]
print(list(product(sizes, colors)))
# [('S', 'red'), ('S', 'blue'), ('M', 'red'), ('M', 'blue'), ('L', 'red'), ('L', 'blue')]
# permutations and combinations
print(list(permutations("ABC", 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
print(list(combinations("ABCD", 2)))
# [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
# groupby (requires sorted input!)
data = sorted([
("А", "Иван"), ("Б", "Мария"), ("А", "Пётр"), ("Б", "Анна")
])
for key, group in groupby(data, key=lambda x: x[0]):
print(f"Группа {key}: {[name for _, name in group]}")
# Группа А: ['Иван', 'Пётр']
# Группа Б: ['Анна', 'Мария']
# starmap - map with unpacking
pairs = [(2, 5), (3, 2), (10, 3)]
results = list(starmap(pow, pairs))
print(results) # [32, 9, 1000]
Композиция функций
from functools import reduce
from typing import Callable, TypeVar
T = TypeVar("T")
def compose(*functions: Callable) -> Callable:
"""Compose functions: compose(f, g, h)(x) = f(g(h(x)))."""
def composed(x):
return reduce(lambda result, func: func(result), reversed(functions), x)
return composed
# Usage
add_one = lambda x: x + 1
double = lambda x: x * 2
to_str = lambda x: f"Result: {x}"
transform = compose(to_str, double, add_one)
print(transform(5)) # 'Result: 12' (5 -> 6 -> 12 -> 'Result: 12')
# Pipe (left-to-right composition)
def pipe(*functions: Callable) -> Callable:
"""Pipe functions: pipe(f, g, h)(x) = h(g(f(x)))."""
def piped(x):
return reduce(lambda result, func: func(result), functions, x)
return piped
process = pipe(
str.strip,
str.lower,
lambda s: s.split(),
lambda words: [w.capitalize() for w in words],
" ".join,
)
print(process(" hello BEAUTIFUL world ")) # 'Hello Beautiful World'
Итоги
- lambda -- анонимные функции для коротких выражений (ключи сортировки, колбэки)
- map/filter -- ФП-альтернативы циклам, но list comprehension обычно предпочтительнее
- reduce -- свёртка последовательности в одно значение
- functools.partial -- фиксация аргументов функции
- operator --
itemgetter,attrgetter,methodcallerдля чистого ФП-стиля - itertools -- мощный набор ленивых итераторов (chain, product, groupby, combinations)
- Композиция функций (
compose/pipe) -- создание конвейеров обработки данных - Python предпочитает comprehensions над map/filter, но ФП-инструменты незаменимы в сложных случаях