Функции в Python: def, return, аргументы, *args и **kwargs, lambda и аннотации типов
Функция в Python — именованный блок кода, который объявляют словом def, вызывают по имени со скобками и из которого возвращают результат через return; без return функция возвращает None. Аргументы передаются по порядку или по имени, *args собирает лишние позиционные значения в кортеж, **kwargs — именованные в словарь, а lambda создаёт короткую анонимную функцию из одного выражения.
Функция в Python — это кусок кода с именем, который можно вызвать сколько угодно раз с разными входными данными. Её объявляют словом def, принимают значения через параметры и отдают результат через return. Функции в Python — главный способ превратить длинную «простыню» скрипта в набор понятных, проверяемых шагов.
Как объявить функцию: def python
Объявление функции начинается с def, затем идут имя, параметры в скобках и двоеточие. Тело пишется с отступом в четыре пробела — отступ в Python не украшение, а часть синтаксиса.
def greet(name):
"""Возвращает приветствие для пользователя."""
return f"Привет, {name}!"
print(greet("Анна")) # Привет, Анна!
Строка в тройных кавычках сразу после заголовка — docstring, документация функции. Её показывает help(greet) и подсказки редактора. Имена функций по соглашению PEP 8 пишут в snake_case: add_expense, get_rate.
Строку def greet(name): называют сигнатурой функции: имя плюс список параметров (и аннотации, если они есть). По сигнатуре читатель понимает, как функцию вызывать, не заглядывая в тело.
Что делает return в Python
return завершает функцию и отдаёт значение тому, кто её вызвал. Код после выполненного return не выполняется. Если return нет или он написан без значения, функция возвращает None.
def area(w, h):
w * h # результат вычислен и выброшен
print(area(3, 4)) # None
def area(w, h):
return w * h
print(area(3, 4)) # 12
Забытый return — одна из самых частых ошибок новичков: функция «работает», но отдаёт None, и ошибка всплывает строчкой ниже. Вернуть можно и несколько значений — на самом деле это один кортеж, который удобно распаковать:
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([4, 1, 9])
print(low, high) # 1 9
Ранний return помогает избавиться от лишней вложенности: сначала отсекаем особые случаи, потом пишем основную логику.
def discount(price, is_member):
if price <= 0:
return 0
if not is_member:
return price
return price * 0.9
Аргументы функции в Python: позиционные и именованные
Параметры — имена в объявлении функции, аргументы — значения, которые передают при вызове. Передать аргументы можно по порядку (позиционно) или по имени (именованно). Именованные аргументы делают вызов самодокументируемым, а порядок перестаёт иметь значение.
def add_expense(title, amount, category="прочее"):
return {"title": title, "amount": amount, "category": category}
add_expense("Кофе", 250) # позиционно
add_expense(amount=250, title="Кофе") # по имени
add_expense("Такси", 600, category="транспорт") # смешанно
Параметр со значением по умолчанию необязателен. Правила: параметры со значением по умолчанию идут после обязательных, а при вызове позиционные аргументы — до именованных. Иначе Python выдаст SyntaxError.
Грабли: изменяемое значение по умолчанию
Значение по умолчанию вычисляется один раз — в момент выполнения строки def, а не при каждом вызове. Если это список или словарь, он будет общим для всех вызовов.
def add_item(item, acc=[]):
acc.append(item)
return acc
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] — список «помнит» прошлый вызов
Правильный приём — None по умолчанию и создание нового объекта внутри:
def add_item(item, acc=None):
if acc is None:
acc = []
acc.append(item)
return acc
*args и **kwargs: любое число аргументов
Звёздочка перед параметром собирает все лишние позиционные аргументы в кортеж (tuple), две звёздочки — все лишние именованные в словарь (dict). Имена args и kwargs — лишь соглашение, важны сами звёздочки.
def total(*args):
return sum(args)
print(total(100, 250, 40)) # 390
def make_user(name, **kwargs):
user = {"name": name}
user.update(kwargs)
return user
print(make_user("Анна", city="Казань", age=30))
# {'name': 'Анна', 'city': 'Казань', 'age': 30}
Те же звёздочки работают в обратную сторону — при вызове они распаковывают последовательность или словарь в аргументы:
values = [3, 4]
print(pow(*values)) # 81
opts = {"sep": " | ", "end": "\n"}
print("a", "b", **opts) # a | b
Только позиционные и только именованные параметры
Одиночная косая черта / и звёздочка * в списке параметров работают как шлагбаумы: всё до / передаётся только по порядку (синтаксис появился в Python 3.8, PEP 570), всё после * — только по имени.
def convert(amount, /, *, rate, currency="USD"):
return round(amount / rate, 2), currency
convert(100, rate=90.5) # (1.1, 'USD')
convert(amount=100, rate=90.5) # TypeError: amount передаётся только позиционно
convert(100, 90.5) # TypeError: rate — только по имени
Только именованные параметры защищают от путаницы в вызовах вроде convert(100, 90.5, "EUR"), где неясно, что есть что.
Область видимости: правило LEGB
Имя переменной Python ищет по цепочке: Local (внутри функции) → Enclosing (во внешней функции) → Global (на уровне модуля) → Built-in (встроенные имена вроде len). Читать глобальную переменную внутри функции можно, а чтобы присвоить ей новое значение, нужно объявление global.
limit = 1000
def check(amount):
return amount > limit # чтение глобальной — можно
def raise_limit():
global limit
limit = 2000 # без global создалась бы локальная переменная
На практике global лучше не использовать: функция, которая меняет внешнее состояние, плохо тестируется. Надёжнее принимать значение аргументом и возвращать результат.
Вложенные функции и замыкания
Функцию можно объявить внутри другой функции. Вложенная функция видит переменные внешней даже после того, как внешняя завершилась, — это и есть замыкание. Для изменения такой переменной служит nonlocal.
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c(), c(), c()) # 1 2 3
Замыкания лежат в основе декораторов и фабрик функций.
Функция — тоже объект
В Python функция — обычный объект: её можно положить в переменную, передать в другую функцию, вернуть из функции, хранить в словаре. У неё есть атрибуты: __name__, __doc__, __defaults__, __annotations__.
def double(x):
return x * 2
f = double
print(f(5), f.__name__) # 10 double
print(list(map(double, [1, 2, 3]))) # [2, 4, 6]
Lambda в Python: анонимные функции
Lambda-функция — короткая функция без имени, тело которой состоит из одного выражения. Результат выражения возвращается автоматически, return писать нельзя.
square = lambda x: x ** 2
print(square(4)) # 16
expenses = [("Кофе", 250), ("Такси", 600), ("Обед", 450)]
expenses.sort(key=lambda e: e[1], reverse=True)
print(expenses) # [('Такси', 600), ('Обед', 450), ('Кофе', 250)]
print(list(filter(lambda n: n % 2 == 0, range(10)))) # [0, 2, 4, 6, 8]
Лямбда-функции в Python уместны как короткий аргумент для sorted, max, map, filter. Присваивать лямбду переменной, как square выше, PEP 8 не рекомендует: если функции нужно имя, пишите def — у неё будет нормальный __name__ в трейсбеке и место для docstring.
| def | lambda | |
|---|---|---|
| Имя | есть | нет (<lambda>) |
| Тело | любое число инструкций | одно выражение |
| return | явный | неявный |
| Docstring, аннотации | да | нет |
| Где уместна | везде | короткий key или колбэк |
Аннотации типов: подсказки, а не проверки
Аннотации описывают ожидаемые типы параметров и результата. Сам Python при запуске их не проверяет: функция с x: int спокойно примет строку. Проверяют аннотации внешние инструменты — mypy, pyright, ty — и редактор, который подсвечивает ошибку ещё до запуска.
def add_expense(title: str, amount: float, category: str = "прочее") -> dict[str, object]:
return {"title": title, "amount": amount, "category": category}
def area(w: float, h: float) -> float:
w * h # mypy: Missing return statement
Запустить проверку без установки можно через uvx mypy .. Пример выше показывает пользу аннотаций: забытый return при -> float анализатор ловит сразу.
С Python 3.12 (PEP 695) обобщённые функции пишутся без TypeVar: параметр типа указывается в квадратных скобках, а псевдоним типа объявляется словом type.
def first[T](xs: list[T]) -> T:
return xs[0]
type Money = int | float
В Python 3.14 (PEP 649) аннотации стали ленивыми: они вычисляются при первом обращении, поэтому ссылаться на класс, объявленный ниже, можно без кавычек, а from __future__ import annotations больше не нужен.
«Не передали» или «передали None»: sentinel в 3.15
Значение None по умолчанию не позволяет отличить «аргумент не передали» от «осознанно передали None». Годами это решали самодельной заглушкой:
_MISSING = object()
def get_rate(currency, default=_MISSING):
rates = {"USD": 90.5}
if currency in rates:
return rates[currency]
if default is _MISSING:
raise KeyError(currency)
return default
В Python 3.15 появилась встроенная функция sentinel (PEP 661): MISSING = sentinel("MISSING") создаёт уникальный маркер с понятным repr, который при копировании остаётся самим собой, сериализуется через pickle и годится в аннотацию вида float | MISSING. На старых версиях продолжает работать приём с object().
Типичные ошибки с функциями
- Забыли return — функция возвращает
None. - Забыли скобки —
print(greet)выведет объект функции, а не результат вызова. - Изменяемое значение по умолчанию — общий список на все вызовы.
- Неверное число аргументов —
TypeError: greet() missing 1 required positional argument: 'name'. - Имя совпадает со встроенным — функция
listилиsumзатенит встроенную.
Итог
Функции в Python объявляются через def, возвращают результат через return и принимают аргументы по порядку или по имени. *args и **kwargs дают гибкость, / и * — строгость вызова, lambda — краткость там, где нужна одноразовая функция, а аннотации типов вместе с mypy ловят ошибки до запуска. В курсе по Python эту тему разбирают на практике: трекер расходов разбивается на функции с аннотациями и проверяется mypy.
Частые вопросы
Чем параметр отличается от аргумента?
Параметр — имя в объявлении функции, аргумент — конкретное значение, переданное при вызове. В def greet(name) name — параметр, в greet("Анна") строка "Анна" — аргумент.
Можно ли вызвать функцию до её объявления?
На уровне модуля — нет: строка def должна выполниться раньше вызова, иначе будет NameError. Но внутри другой функции можно вызывать функцию, объявленную ниже, если вызов произойдёт после её объявления.
Сколько значений может вернуть функция?
Формально одно, но этим значением может быть кортеж: return a, b возвращает кортеж из двух элементов, который распаковывают как x, y = f().
Обязательно ли писать аннотации типов?
Нет, Python работает и без них. Но в коде, который читают другие люди, аннотации заметно упрощают поддержку и позволяют mypy или pyright находить ошибки до запуска.
Можно ли в lambda написать if?
Можно только условное выражение: lambda x: "чёт" if x % 2 == 0 else "нечет". Инструкции, циклы и присваивания внутри lambda недопустимы.
Эта тема подробно разобрана с практикой в курсе «Python 3.15: от новичка до профи».
Перейти к курсу