Перейти до змісту

Функції та функціональне програмування

Довідник по функціях у Python — від базового визначення до замикань, декораторів і функціонального стилю.

1. Базові концепції функцій

Функція — це іменований блок коду, призначений для вирішення однієї конкретної задачі. Використання функцій дозволяє уникнути дублювання коду (принцип DRY — Don't Repeat Yourself), робить програму більш читабельною, полегшує її тестування та супровід.

Створення та виклик

У Python функції визначаються за допомогою ключового слова def, за яким слідує ім'я функції, круглі дужки з параметрами та двокрапка. Тіло функції виділяється відступами. Функція не виконується в момент її визначення; код усередині неї запускається лише тоді, коли функція викликається.

Повернення значень (return)

Функції можуть обчислювати результат і повертати його в основну програму за допомогою інструкції return. Якщо функція не містить return, вона за замовчуванням повертає спеціальне значення None. Також Python дозволяє повертати одразу декілька значень (вони автоматично пакуються в кортеж).

Документування (Docstrings)

Гарним тоном є використання рядків документації (docstrings) — багаторядкових коментарів у потрійних лапках """...""", які розміщуються одразу після визначення def. Цей текст автоматично зберігається у спеціальному атрибуті функції __doc__. Прочитати його можна за допомогою вбудованої функції help(ім'я_функції).

def greet_user(name):
    """
    Виводить просте вітання для вказаного користувача.
    """
    return f"Hello, {name}!"

# Виклик функції
message = greet_user("Ivan")
print(message)

# Перегляд документації
help(greet_user)

Результат:

Hello, Ivan!
Help on function greet_user in module __main__:

greet_user(name)
    Виводить просте вітання для вказаного користувача.

2. Робота з аргументами та параметрами

Python має надзвичайно гнучкий механізм передачі аргументів.

  • Позиційні та іменовані аргументи: аргументи можна передавати в тому порядку, в якому вони визначені (позиційні), або вказуючи їхні імена (іменовані), що робить порядок неважливим.
  • Значення за замовчуванням: параметрам можна задати значення за замовчуванням (наприклад, def func(a, b=2):). Тоді при виклику цей аргумент можна пропустити.

    Мутабельні значення за замовчуванням

    Ніколи не використовуйте змінні об'єкти (як-от порожні списки []) як значення за замовчуванням, оскільки вони зберігатимуть свій стан між викликами. Замість цього використовуйте None.

  • Довільна кількість аргументів (*args та **kwargs):

    • символ * перед параметром (зазвичай *args) збирає всі додаткові позиційні аргументи в кортеж;
    • символи ** (зазвичай **kwargs) збирають додаткові іменовані аргументи у словник.
def my_func(a, b=10, *args, **kwargs):
    print("a:", a)
    print("b:", b)
    print("args:", args)
    print("kwargs:", kwargs)

my_func(1, 20, 30, 40, x=100, y=200)

Результат:

a: 1
b: 20
args: (30, 40)
kwargs: {'x': 100, 'y': 200}

Повний порядок параметрів у def

def example(pos_only, /, pos_or_kw, default_pos=10, *args, kw_only, kw_default=20, **kwargs):
    pass
Вид Позначка Передача Тип усередині
Positional-only (3.8+, PEP 570) ліворуч від / лише за позицією значення
Positional-or-keyword після /, до *args / * за позицією або за іменем значення
Var-positional *args збирає зайві позиційні tuple
Keyword-only (PEP 3102) після *args або поодинокої * лише key=value значення
Var-keyword **kwargs, завжди останній збирає зайві іменовані dict

Значення за замовчуванням — не окремий вид: його можуть мати і positional-or-keyword (default_pos=10), і keyword-only (kw_default=20) параметри. Серед параметрів, які передаються за позицією, параметри зі значенням за замовчуванням стоять після обов'язкових. Для keyword-only такого обмеження немає.

Порушення порядку — SyntaxError ще до запуску:

def f(a=1, b): ...        SyntaxError: parameter without a default follows parameter with a default
def f(**kw, *args): ...   SyntaxError: arguments cannot follow var-keyword argument

Правила виклику

  • Спершу позиційні, потім іменовані: f(a=1, 2) → SyntaxError: positional argument follows keyword argument.
  • Одне значення — один раз: f(1, a=2) → TypeError: f() got multiple values for argument 'a'.
  • Іменовані аргументи можна передавати в будь-якому порядку.

Розпаковка * і **

Де Запис Результат
виклик f(*[1, 2, 3]) f(1, 2, 3); працює з list, tuple, set (порядок не гарантований), str, range, генератором; dict віддає ключі
виклик f(**{"x": 1}) f(x=1); ключі мають збігатися з іменами параметрів
присвоєння first, *rest = [1, 2, 3] first = 1, rest = [2, 3] (завжди list)
літерал [*a, *b], (*a, *b), {*a, *b} нова колекція з елементів a і b
літерал {**d1, **d2} злиття словників, при однакових ключах перемагає правий
прокидання def w(*args, **kwargs): return f(*args, **kwargs) посередник з будь-якою сигнатурою (основа декораторів)

Пастки

  • Мутабельне значення за замовчуванням обчислюється один раз при виконанні def і зберігається в func.__defaults__ (для keyword-only — у __kwdefaults__). Виправлення: None + новий об'єкт у тілі.
  • Call-by-sharing: параметр — ще одне ім'я для того самого об'єкта. Присвоєння x = ... всередині ззовні не видно, а мутацію x.append(...) видно.
  • **kwargs ковтає описки: debyg=True мовчки потрапляє в словник. Якщо імена відомі, оголошуй їх явно як keyword-only: def f(data, *, debug=False).

Детальні приклади з логуванням: урок 8 та конспект module_1/lessons/lesson_08_functions/notes_functions.ipynb, Частина 6.

3. Область видимості (Scopes)

Змінні, створені всередині функції, є локальними і не існують за її межами. Якщо функція намагається знайти змінну, Python шукає її за правилом LEGB:

  1. Local (Локальна) — всередині функції.
  2. Enclosing (Охоплююча) — у зовнішній функції (якщо функції вкладені).
  3. Global (Глобальна) — на рівні модуля/файлу.
  4. Built-in (Вбудована) — базові імена Python (наприклад, len, print).

Щоб змінити глобальну змінну всередині функції, використовується ключове слово global. Для зміни змінної з охоплюючої (зовнішньої) функції використовується nonlocal.

👉 Це лише вступ, потрібний для розуміння наступних розділів. Повний розбір LEGB (з покроковими прикладами кожного рівня, пастками на кшталт UnboundLocalError і тим, що саме повертають locals()/globals()) — на окремій сторінці: Простори імен: locals(), globals(), LEGB.

4. Функціональне програмування (FP) в Python

Хоча Python не є суто функціональною мовою програмування, він містить потужний арсенал інструментів для написання коду у функціональному стилі. Основа ФП — обчислення без зміни глобального стану (використання "чистих функцій", які не мають побічних ефектів).

Функції як об'єкти першого класу

У Python абсолютно все є об'єктами, і функції — не виняток. Функцію можна присвоїти змінній, передати як аргумент іншій функції (функції вищого порядку) або повернути як результат.

Анонімні функції (lambda)

Для створення коротких, одноразових функцій використовується оператор lambda. Вони містять лише один вираз, результат якого автоматично повертається.

Основні інструменти: map, filter, reduce

  • map(func, iterable) — застосовує функцію до кожного елемента послідовності.
  • filter(func, iterable) — повертає лише ті елементи, для яких функція повернула True.
  • reduce(func, iterable) — послідовно застосовує функцію до пар елементів, "згортаючи" послідовність до одного значення. Потребує імпорту з functools.

List comprehensions замість map/filter

У сучасному Python замість map та filter часто віддають перевагу генераторам списків (list comprehensions), оскільки вони читаються легше.

from functools import reduce

# Лямбда-функція
add_lambda = lambda x, y: x + y
print("Лямбда 5+3 =", add_lambda(5, 3))

nums = [1, 2, 3, 4, 5]

# Map: підносимо до квадрату
squares = list(map(lambda x: x**2, nums))
print("Map (квадрати):", squares)

# Filter: залишаємо лише парні
evens = list(filter(lambda x: x % 2 == 0, nums))
print("Filter (парні):", evens)

# Reduce: сума всіх чисел
total = reduce(lambda a, b: a + b, nums)
print("Reduce (сума):", total)

Результат:

Лямбда 5+3 = 8
Map (квадрати): [1, 4, 9, 16, 25]
Filter (парні): [2, 4]
Reduce (сума): 15

Корисні модулі для ФП: operator та functools

  • Модуль operator — містить функції, що відповідають стандартним математичним операторам. Замість lambda x, y: x + y можна імпортувати operator.add.
  • functools.partial — дозволяє "зафіксувати" частину аргументів функції, створивши нову функцію з меншою кількістю параметрів (часткове застосування).
from functools import partial
import operator

def power(base, exp):
    return base ** exp

# Створюємо нову функцію, де exp завжди дорівнює 2
square = partial(power, exp=2)
print("Partial (5^2):", square(5))

# Використання operator замість lambda в reduce
total_op = reduce(operator.add, nums)
print("Reduce з operator.add:", total_op)

Результат:

Partial (5^2): 25
Reduce з operator.add: 15

5. Просунуті техніки на базі функцій

  • Вкладені функції та замикання (closures): Python дозволяє визначати функцію всередині іншої функції. Вкладена функція може запам'ятовувати та використовувати змінні із зовнішньої (охоплюючої) функції навіть після того, як зовнішня функція завершила свою роботу. Це явище і називається замиканням.
  • Декоратори: спеціальні функції-обгортки, які приймають іншу функцію як аргумент, модифікують її поведінку і повертають нову функцію. Застосовуються за допомогою символу @ перед оголошенням def. Ідеально для логування або кешування.
  • Рекурсія: функція може викликати саму себе. Корисно для обробки деревоподібних структур даних або математичних алгоритмів (наприклад, факторіал), проте завжди потрібно передбачати умову виходу, щоб уникнути нескінченного циклу.
# --- Приклад 1: Декоратор ---
def uppercase_decorator(func):
    def wrapper(*args, **kwargs):
        original_result = func(*args, **kwargs)
        return original_result.upper()  # Змінюємо поведінку (робимо великими літерами)
    return wrapper

@uppercase_decorator
def say_hi(name):
    return f"Привіт, {name}"

print("Декоратор:", say_hi("Олексій"))


# --- Приклад 2: Рекурсія ---
def factorial(n):
    if n == 1:  # Умова виходу (Base case)
        return 1
    return n * factorial(n - 1)  # Рекурсивний виклик

print("\nРекурсія (5!):", factorial(5))

Результат:

Декоратор: ПРИВІТ, ОЛЕКСІЙ

Рекурсія (5!): 120

👉 Практичне застосування функцій на курсі — рефакторинг звіту кафе у Уроці 7; декоратори як окрема тема — Урок 9.