Перейти к содержимому
Главная страница

Функции в Python: определение, вызов и приёмы для построения переиспользуемого кода

Логотип языка программирования Python на синем фоне с надписью «PYTHON»

Содержание:

Функция

Функции — основной механизм структурирования программ в 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.

Базовый шаблон и неявный 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 поддерживает позиционные-только, позиционные/именованные, именованные-только параметры и вариативные. Разделители: символ / вводит позиционные-только, символ * — границу, после которой параметры принимаются только по имени.

Современные сигнатуры (Python 3.8+)
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 — чаще лучше возвращать результат или инкапсулировать состояние в объект.

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).
  • Минимизируйте скрытые побочные эффекты; явно возвращайте новые объекты.
  • Гвард-клаусы: ранний выход упрощает чтение.
  • Покрывайте ветвления тестами; добавляйте примеры в докстринг.
Guard-clauses вместо вложенной «лесенки»
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 и создавайте новый контейнер внутри.

Итоги

Функции делают код модульным и предсказуемым. Освойте современные сигнатуры (позиционные-только, именованные-только), аккуратно используйте дефолтные значения, применяйте замыкания и декораторы, документируйте и типизируйте интерфейсы, покрывайте ветви тестами. Чем понятнее и короче функции, тем легче расширять систему и быстрее находить ошибки. 💡

0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

Достигнут лимит времени. Пожалуйста, введите CAPTCHA снова.

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии