
Определение и вызов функций в Python: создание reusable кода для организации и упрощения программ
Функции — это фундамент «переиспользуемости» в Python: они группируют логику в именованные блоки, уменьшают дублирование, повышают читаемость и позволяют тестировать программу по частям. В этой статье вы последовательно разберёте синтаксис def и return, позиционные и именованные аргументы, параметры по умолчанию, вариативные *args/**kwargs, область видимости, lambda и рекурсию, а также декораторы и лучшие практики. Всё снабжено примерами и мини-практикумом, а в конце. Поехали! 🚀
Что такое функция в Python
Функция — это именованный блок кода, который принимает входные значения (аргументы), выполняет действия и возвращает результат. Благодаря функциям код становится модульным: вы разделяете программу на небольшие «кирпичики», каждый со своей ответственностью. Это упрощает отладку, повторное использование и тестирование.
Идеи, на которых строится работа с функциями: абстракция (скрываем детали реализации за именем), инкапсуляция (внутренние переменные не «торчат» наружу), разделение ответственности (каждая функция делает одну понятную вещь), композиция (сложную логику собираем из простых частей).
# Зачем функции? Повторное использование и читаемость
def normalize_name(raw):
text = raw.strip()
return text.capitalize()
print(normalize_name(" алиса ")) # Алиса
Синтаксис определения функции: def, параметры и return
Определение функции начинается с ключевого слова def, за которым следует имя, список параметров в скобках и двоеточие. Тело функции — блок с отступами. Оператор return завершает выполнение и передаёт результат вызывающему коду. Если return не указан, функция возвращает None (неявно).
# Базовый шаблон
def add(a, b):
result = a + b
return result
x = add(2, 3) # 5
Имена функций оформляйте в стиле snake_case, а в теле оставляйте «короткую» и очевидную логику. Если функция стала «толстой», разбейте её на несколько подфункций.
# Функция без return явно возвращает None
def greet(name):
print(f"Привет, {name}!")
val = greet("Алиса")
print(val is None) # True
Вызов функций: позиционные и именованные аргументы
Аргументы можно передавать позиционно (по порядку) и по имени (именованные). Именованные аргументы улучшают читаемость вызовов и уменьшают риск перепутать порядок.
def connect(host, port, use_ssl):
return f"{host}:{port} ssl={use_ssl}"
# позиционный вызов
print(connect("example.com", 443, True))
# именованный вызов
print(connect(host="example.com", port=443, use_ssl=True))
Правила порядка: сначала позиционные аргументы, затем именованные. Для длинных списков параметров стройте вызов столбиком — так легче читать диффы в коммитах.
# Рекомендация: сложные вызовы переносить по строкам
user = create_user(
email="a@example.com",
password="S3cret",
is_admin=False,
)
Параметры по умолчанию, *args и **kwargs
Значения по умолчанию упрощают вызовы: вы задаёте «разумные» дефолты, а пользователь функции переопределяет только то, что ему важно.
# Значения по умолчанию
def paginate(page=1, per_page=20):
start = (page - 1) * per_page
return start, per_page
*args собирает произвольное количество позиционных аргументов в кортеж, **kwargs — именованные аргументы в словарь. Это удобно, когда количество параметров заранее неизвестно или API должен быть гибким.
# Вариативные аргументы
def summarize(*nums, **opts):
total = sum(nums)
prefix = opts.get("prefix", "sum")
return f"{prefix}={total}"
print(summarize(1, 2, 3)) # sum=6
print(summarize(1, 2, 3, prefix="Σ")) # Σ=6
Осторожнее с изменяемыми дефолтами: list, dict, set. Их следует задавать через None и инициализировать внутри.
# Антипример и исправление
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
Область видимости: локальные, глобальные, 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
Совет по стилю: минимизируйте использование global и nonlocal; вместо этого возвращайте значения или инкапсулируйте состояние в объекты/классы.
Анонимные функции (lambda): когда использовать
lambda задаёт короткую безымянную функцию. Используйте её, когда нужна компактная «операция» здесь и сейчас — например, в sorted, map, обработчиках событий.
# Короткая функция ключа сортировки
users = [{"name":"bob","age":30},{"name":"ann","age":20}]
print(sorted(users, key=lambda u: u["age"]))
Ограничения: только одно выражение, без многострочных конструкций. Если логика разрастается, выделяйте именованную функцию через def.
Рекурсия: принцип и примеры
Рекурсивная функция вызывает сама себя, пока не достигнет базового случая. Она удобна при обходе деревьев, разборе вложенных структур и математических задачах (факториал, Фибоначчи), но требует контроля глубины и условий выхода.
# Факториал через рекурсию
def fact(n):
if n < 2:
return 1
return n * fact(n-1)
# Хвостовая рекурсия в CPython не оптимизируется — используйте цикл
def fact_iter(n):
res = 1
for k in range(2, n+1):
res *= k
return res
Следите за терминальным условием, чтобы не получить Recursionerror. Для глубоких структур иногда проще применить стек/очередь и обычные циклы.
Встроенные и пользовательские функции
Встроенные функции Python (len, sum, min, max, any, all, sorted и др.) закрывают частые сценарии. Пользовательские функции нужны, чтобы выразить предметную логику вашей задачи.
# Комбинируем встроенные и свои
def avg(xs):
return sum(xs)/len(xs) if xs else 0.0
print(avg([1,2,3])) # 2.0
Антипаттерн — писать «велосипед» вместо использования встроенных средств. Прежде чем реализовать свою функцию, посмотрите стандартную библиотеку.
Декораторы: расширение поведения без изменения кода
Декоратор — это функция, которая принимает другую функцию и возвращает новую, «обёрнутую» версию. Так можно добавлять кросс-функциональные аспекты (логирование, кеширование, тайминг) без правки исходника.
import time
def timing(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()
В реальных проектах полезны functools.wraps (сохраняет имя и докстринг декорируемой функции) и готовые декораторы вроде lru_cache для кеширования.
Лучшие практики: читаемость, SRP, docstring, тестирование
- Одна задача — одна функция. Если функция делает много всего, разделите на подфункции.
- Именование. Глаголы для действий (load_data, save_user), существительные — для вычислений (distance, checksum).
- Документируйте. Используйте docstring в первой строке тела функции: кратко, по делу, с описанием параметров/возврата.
- Не мутируйте аргументы неожиданно. Возвращайте новые объекты или явно указывайте на побочный эффект.
- Типизация. Аннотации типов помогают IDE и рецензентам.
- Тестируйте. На каждую ветку — отдельный тест (включая ошибки).
# Docstring и аннотации типов
def clamp(x: float, low: float, high: float) -> float:
"""Ограничивает x интервалом [low, high]. Возвращает число."""
return max(low, min(high, x))
# Пример простого теста (pytest)
def test_clamp():
assert clamp(5, 0, 10) == 5
assert clamp(-1, 0, 10) == 0
assert clamp(99, 0, 10) == 10
Мини-практикум: 4 задачи для закрепления
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) Нормализатор данных
def normalize(items, *, unique=False, key=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):
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
def retry(times=3, delay=0.1):
def deco(fn):
def wrap(*a, **kw):
for attempt in range(times):
try:
return fn(*a, **kw)
except Exception as e:
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())
FAQ
Собрали ответы на самые частые вопросы о функциях в Python — раскройте нужный спойлер 👇
В Python любая функция, где нет оператора return (или он вызывается без значения), возвращает None. Если вы видите None вместо результата — проверьте ветвления: возможно, в какой-то ветке вы просто выводите print и забываете return.
def bad_add(a, b):
if a and b:
print(a+b) # забыли return!
res = bad_add(2,3); print(res) # None
Когда заранее не знаете количество параметров или хотите дать вызывающей стороне гибкость именованных опций. Главное — не скрывайте важные параметры в **kwargs: ключевые аргументы лучше сделать явными, а «настройки» оставить вариативными.
lambda создаёт короткую безымянную функцию, ограниченную одним выражением; def объявляет именованную функцию с телом любой сложности. Для одноразовых ключей сортировки и маленьких преобразований берите lambda; для всего остального — def.
Краткий docstring в первой строке тела функции: что делает, какие параметры, что возвращает и исключения (если есть). Для публичных API добавьте примеры использования.
def area(w: float, h: float) -> float:
"""Вычисляет площадь прямоугольника. :param w: ширина :param h: высота :return: площадь"""
return w*h
Они не влияют на исполнение, но помогают IDE, статическому анализу и читателям кода. Особенно полезны в командах и для публичных библиотек.
Если структура задачи естественно рекурсивна (деревья, разбор вложенных выражений) — рекурсия выразительнее. Когда глубина велика или требуется контроль над производительностью — используйте циклы и собственные стеки.
Потому что они инициализируются один раз при определении функции и «живут» между вызовами. Используйте None и создавайте новый объект внутри.
Для каждого сценария — отдельный тест, включая негативные. Покрывайте функции со скрытыми ветками (условия, исключения). Настройте быстрый прогон тестов перед коммитом.
Заключение и полезные ресурсы
Вы разобрали весь жизненный цикл функций: от определения через def и аккуратного return до продвинутых тем *args/**kwargs, области видимости, lambda, рекурсии и декораторов. Дальше прокачивайте стилистику (PEP 8, аннотации типов), покрывайте код тестами и практикуйтесь в разбиении задач на маленькие «функциональные» шаги. Чем чище и короче ваши функции, тем понятнее программа и тем быстрее её развивать 🙂
- Документация Python: раздел о функциях, аргументах и области видимости.
- PEP 8: рекомендации по стилю кода и именованию.
- Практика: задачи на функции (Codewars, LeetCode, HackerRank) — по 20–40 минут в день.