Функция
Функции — основной механизм структурирования программ в Python. Они позволяют сгруппировать логику под понятным именем, сократить дублирование, улучшить читаемость и изолировать поведение для тестирования. Ниже — полный разбор: синтаксис def и return, позиционные и именованные аргументы, значения по умолчанию, вариативные параметры *args/**kwargs, области видимости, замыкания, lambda, рекурсия, декораторы, типизация и практики качества. Всё сопровождается примерами и упражнениями. 🚀
Что такое функция
Функция — это именованный блок кода, который принимает аргументы, выполняет действия и возвращает результат. Ключевые свойства: абстракция деталей, инкапсуляция состояния и композиция из простых шагов. Чем меньше побочных эффектов и чем уже ответственность, тем проще сопровождать код.
def normalize_name(raw: str) -> str:
text = raw.strip()
return text.capitalize()
print(normalize_name(" алиса ")) # Алиса
Синтаксис: def, параметры и return
Определение начинается с def и имени в стиле snake_case; список параметров заключён в скобки; тело отделяется двоеточием и отступами. Оператор return завершает выполнение и передаёт значение. Если return не указан — возвращается None.
def add(a: float, b: float) -> float:
return a + b
def greet(name: str) -> None:
print(f"Привет, {name}!")
print(add(2, 3)) # 5
print(greet("Алиса")) # None
Вызов функций: позиционные и именованные аргументы
Аргументы можно передавать по позиции и по имени. Именованные аргументы повышают читаемость и снижают риск перепутать порядок.
def connect(host: str, port: int, use_ssl: bool) -> str:
return f"{host}:{port} ssl={use_ssl}"
print(connect("example.com", 443, True))
print(connect(host="example.com", port=443, use_ssl=True))
Виды параметров в сигнатуре
Python поддерживает позиционные-только, позиционные/именованные, именованные-только параметры и вариативные. Разделители: символ / вводит позиционные-только, символ * — границу, после которой параметры принимаются только по имени.
def f(a, b, /, c, d, *, e, f_):
# a, b — только позиционно; c, d — позиционно или по имени; e, f_ — только по имени
return a + b + c + d
Значения по умолчанию, *args и **kwargs
Значения по умолчанию сокращают количество обязательных аргументов. *args собирает произвольное число позиционных параметров в кортеж, **kwargs — именованные в словарь.
def paginate(page: int = 1, per_page: int = 20) -> tuple[int, int]:
start = (page - 1) * per_page
return start, per_page
def summarize(*nums: float, **opts) -> str:
total = sum(nums)
prefix = opts.get("prefix", "sum")
return f"{prefix}={total}"
print(paginate()) # (0, 20)
print(summarize(1, 2, 3, prefix="Σ")) # Σ=6
def append_bad(item, bucket=[]):
bucket.append(item)
return bucket # состояние «залипает» между вызовами
def append_ok(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
Распаковка аргументов и управление вызовами
Последовательности можно распаковать как позиционные, словари — как именованные аргументы. Это удобно при передачах параметров «сквозь» функции.
def area(w: float, h: float) -> float:
return w * h
args = (3, 4)
kwargs = {"w": 5, "h": 6}
print(area(*args)) # 12
print(area(**kwargs)) # 30
Область видимости: локальные, глобальные, nonlocal
Имена, созданные в функции, локальны. Чтение глобальных переменных допустимо; для записи используйте global. Во вложенных функциях можно менять переменные внешней функции через nonlocal. Сократите использование global/nonlocal — чаще лучше возвращать результат или инкапсулировать состояние в объект.
counter = 0
def tick():
global counter
counter += 1
def outer():
x = 0
def inner():
nonlocal x
x += 1
inner(); inner()
return x # 2
Замыкания и функции высшего порядка
Функции — объекты первого класса: их можно передавать, возвращать и хранить в структурах данных. Замыкание захватывает из внешней области используемые переменные.
from typing import Callable
def make_counter() -> Callable[[], int]:
n = 0
def inc():
nonlocal n
n += 1
return n
return inc
c = make_counter()
print(c(), c(), c()) # 1 2 3
Анонимные функции (lambda)
lambda задаёт короткую безымянную функцию из одного выражения. Она удобна для ключей сортировки, простых преобразований и «инлайновых» колбэков. Если логика разрастается — переходите на def.
users = [{"name":"bob","age":30},{"name":"ann","age":20}]
print(sorted(users, key=lambda u: (u["age"], u["name"])))
Рекурсия и итерация
Рекурсивные решения естественны для деревьев и вложенных структур, но требуют базового случая и контроля глубины стека. В CPython оптимизации хвостовой рекурсии нет, поэтому для длинных последовательностей используйте циклы.
def fact(n: int) -> int:
if n < 2:
return 1
return n * fact(n - 1)
def fact_iter(n: int) -> int:
res = 1
for k in range(2, n + 1):
res *= k
return res
Декораторы: расширяем поведение без правки исходника
Декоратор принимает функцию и возвращает новую, «обёрнутую», добавляя кросс-функциональные аспекты: логирование, кеширование, авторизацию, измерение времени.
import time
from functools import wraps
def timing(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
dt = (time.perf_counter() - t0) * 1000
print(f"{fn.**name**} took {dt:.1f} ms")
return wrapper
@timing
def heavy():
sum(range(1_000_00))
heavy()
from functools import lru_cache
@lru_cache(maxsize=1024)
def fib(n: int) -> int:
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
Типизация и документация
Аннотации типов помогают IDE и статическим анализаторам, а докстринги объясняют назначение и контракт функции. Придерживайтесь единого формата (Google/NumPy/reST), и описывайте параметры, возвращаемое значение и исключения.
def clamp(x: float, low: float, high: float) -> float:
"""Ограничивает x интервалом [low, high]."""
return max(low, min(high, x))
Ошибки и исключения
Исключительные ситуации обрабатывайте через try/except, а не через громоздкие ветвления. Логи фиксируют контекст и ускоряют диагностику.
import logging
logging.basicConfig(level=logging.INFO)
def price_from_str(s: str) -> float:
try:
val = float(s)
if val < 0:
raise ValueError("negative")
logging.info("ok: %s", val)
return val
except ValueError as e:
logging.error("parse error: %s", e)
return 0.0
Практики качества
- SRP: одна функция — одна задача.
- Понятные имена: глаголы для действий (load_user), существительные для вычислений (distance).
- Минимизируйте скрытые побочные эффекты; явно возвращайте новые объекты.
- Гвард-клаусы: ранний выход упрощает чтение.
- Покрывайте ветвления тестами; добавляйте примеры в докстринг.
def can_access(user: dict | None) -> bool:
if not user:
return False
if not user.get("active"):
return False
return user.get("role") == "admin"
Инструменты функционального стиля
Модуль functools предлагает полезные утилиты: partial для частичного применения, cmp_to_key для старых сравнителей, lru_cache для мемоизации.
from functools import partial
def power(base: int, exp: int) -> int:
return base ** exp
square = partial(power, exp=2)
print(square(7)) # 49
Мини-практикум: 6 задач для закрепления
1) Форматтер строк с *args и **kwargs
def fmt(*parts, **opts):
sep = opts.get("sep", " ")
end = opts.get("end", "!")
return sep.join(map(str, parts)) + end
print(fmt("Hello", "Python", sep="-", end=" 🚀"))
2) Нормализатор коллекций
from typing import Iterable, Callable, Any
def normalize(items: Iterable[Any], *, unique=False, key: Callable | None = None):
xs = [key(x) if key else x for x in items]
return list(dict.fromkeys(xs)) if unique else xs
print(normalize([" A ","A","b"], unique=True, key=lambda s: s.strip().lower()))
3) Глубина вложенных структур
def deep_len(x) -> int:
if isinstance(x, (list, tuple, set)):
return sum(deep_len(i) for i in x)
return 1
print(deep_len([1,(2,3),[4,[5,6]]])) # 6
4) Декоратор повторных попыток
import random, time
from functools import wraps
def retry(times=3, delay=0.1):
def deco(fn):
@wraps(fn)
def wrap(*a, **kw):
for attempt in range(times):
try:
return fn(*a, **kw)
except Exception:
if attempt + 1 == times:
raise
time.sleep(delay)
return wrap
return deco
@retry(times=5, delay=0.05)
def flaky():
if random.random() < 0.7:
raise RuntimeError("fail")
return "ok"
print(flaky())
5) Композиция функций
from typing import Callable
def compose(f: Callable, g: Callable) -> Callable:
return lambda x: f(g(x))
strip_cap = compose(str.capitalize, str.strip)
print(strip_cap(" python ")) # Python
6) Проверка контрактов
def require_positive(fn):
def wrap(x: float):
assert x >= 0, "x must be non-negative"
return fn(x)
return wrap
@require_positive
def sqrt_approx(x: float) -> float:
# примитивная аппроксимация Ньютона
g = x or 1.0
for _ in range(10):
g = 0.5 * (g + x / g)
return g
print(round(sqrt_approx(9), 3))
FAQ
Почему функция возвращает None?
В ветке отсутствует return или он вызывается без значения. Проверьте условия и убедитесь, что из каждой ветви возвращаете результат.
Когда использовать *args и **kwargs?
Когда количество параметров заранее неизвестно или требуется гибкость. Важные аргументы делайте явными, а «настройки» передавайте через **kwargs.
Чем lambda отличается от def?
lambda — одно выражение без имени; def — именованная функция с произвольным телом. Для простых ключей и однострочных преобразований подходит lambda, всё остальное — через def.
Как документировать функции?
Краткий докстринг с назначением, параметрами и возвратом. Для публичного API добавьте примеры. Держите формат единым по проекту.
Зачем типовые аннотации?
Они не влияют на выполнение, но повышают качество рецензий, снижают число ошибок и улучшают подсказки IDE.
Рекурсия или цикл?
Рекурсия уместна на деревьях и разборе вложенных структур; для длинных последовательностей надёжнее цикл или явный стек.
Почему изменяемые дефолты — проблема?
Потому что объект создаётся один раз при определении функции и «копится» между вызовами. Используйте None и создавайте новый контейнер внутри.
Итоги
Функции делают код модульным и предсказуемым. Освойте современные сигнатуры (позиционные-только, именованные-только), аккуратно используйте дефолтные значения, применяйте замыкания и декораторы, документируйте и типизируйте интерфейсы, покрывайте ветви тестами. Чем понятнее и короче функции, тем легче расширять систему и быстрее находить ошибки. 💡
