← Назад к вопросам

Какие знаешь критерии чистой функции?

2.3 Middle🔥 181 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Критерии чистой функции (Pure Function)

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

Основные критерии чистой функции

1. Детерминированность (Determinism)

Для одних и тех же входных данных функция всегда возвращает одинаковый результат.

# Чистая функция
def add(a: int, b: int) -> int:
    return a + b

print(add(2, 3))  # 5
print(add(2, 3))  # 5 (всегда одно и то же)

# Нечистая функция (зависит от времени)
import time

def get_current_time() -> float:
    return time.time()

print(get_current_time())  # 1234567890.123
print(get_current_time())  # 1234567890.456 (разные результаты)

2. Отсутствие побочных эффектов (No Side Effects)

Функция не должна изменять состояние вне её области видимости.

# Нечистая функция (изменяет глобальное состояние)
counter = 0

def increment_counter():
    global counter
    counter += 1  # ПОБОЧНЫЙ ЭФФЕКТ!
    return counter

# Чистая функция (не изменяет состояние)
def increment(value: int) -> int:
    return value + 1

result = increment(5)  # 6 (не меняет ничего вне функции)

3. Независимость от внешнего состояния

Функция не должна зависеть от переменных, которые могут измениться.

# Нечистая функция
config = {"multiplier": 2}

def multiply(x: int) -> int:
    return x * config["multiplier"]  # Зависит от config!

config["multiplier"] = 10
print(multiply(5))  # 50 (результат изменился)
config["multiplier"] = 2

# Чистая функция
def multiply(x: int, multiplier: int) -> int:
    return x * multiplier

print(multiply(5, 2))  # 10 (всегда одно и то же)

Признаки нечистых функций

1. Изменение аргументов (mutating arguments)

# Нечистая функция
def add_to_list(lst: list, item):
    lst.append(item)  # Изменяет переданный список!

my_list = [1, 2, 3]
add_to_list(my_list, 4)
print(my_list)  # [1, 2, 3, 4] — изменился!

# Чистая функция
def add_to_list(lst: list, item):
    return lst + [item]  # Создаёт новый список

my_list = [1, 2, 3]
new_list = add_to_list(my_list, 4)
print(my_list)     # [1, 2, 3] — не изменился
print(new_list)    # [1, 2, 3, 4]

2. Побочные эффекты с файлами и БД

# Нечистая функция
def process_user(user_id: int) -> dict:
    user = get_user_from_db(user_id)  # Побочный эффект (запрос к БД)
    user['processed'] = True
    save_to_db(user)                  # Побочный эффект (запись в БД)
    log_to_file(f"Processed {user_id}")  # Побочный эффект (запись в файл)
    return user

# Чистая функция
def process_user_data(user: dict) -> dict:
    processed_user = user.copy()
    processed_user['processed'] = True
    return processed_user

# Логика с побочными эффектами отделена
def process_user_with_effects(user_id: int) -> dict:
    user = get_user_from_db(user_id)
    processed = process_user_data(user)  # Чистая часть
    save_to_db(processed)
    log_to_file(f"Processed {user_id}")
    return processed

3. Вывод на печать и логирование

# Нечистая функция
def calculate_sum(a: int, b: int) -> int:
    print(f"Calculating {a} + {b}")  # Побочный эффект!
    return a + b

# Чистая функция
def calculate_sum(a: int, b: int) -> int:
    return a + b

# Логирование отделено
result = calculate_sum(2, 3)
print(f"Result: {result}")

Практические примеры

Пример 1: Обработка данных

# Нечистая версия
data = [1, 2, 3, 4, 5]

def process_data():
    global data  # Зависит от глобального состояния!
    data = [x * 2 for x in data]
    print(f"Processed: {data}")  # Побочный эффект
    return sum(data)

total = process_data()  # Изменил глобальную переменную!

# Чистая версия
def process_data(data: list) -> dict:
    doubled = [x * 2 for x in data]
    return {"data": doubled, "sum": sum(doubled)}

original_data = [1, 2, 3, 4, 5]
result = process_data(original_data)
print(result)  # {"data": [2, 4, 6, 8, 10], "sum": 30}
print(original_data)  # [1, 2, 3, 4, 5] — не изменился

Пример 2: Работа со словарями

# Нечистая функция
def update_user(user: dict, name: str) -> dict:
    user['name'] = name  # Изменяет исходный объект!
    return user

user = {'id': 1, 'name': 'Alice'}
update_user(user, 'Bob')
print(user)  # {'id': 1, 'name': 'Bob'} — изменился!

# Чистая функция
def update_user(user: dict, name: str) -> dict:
    return {**user, 'name': name}  # Создаёт новый словарь

user = {'id': 1, 'name': 'Alice'}
updated_user = update_user(user, 'Bob')
print(user)         # {'id': 1, 'name': 'Alice'} — не изменился
print(updated_user) # {'id': 1, 'name': 'Bob'}

Преимущества чистых функций

1. Легче тестировать

# Чистая функция легко тестировать
def add(a: int, b: int) -> int:
    return a + b

assert add(2, 3) == 5
assert add(0, 0) == 0
assert add(-1, 1) == 0

# Нечистую функцию сложно тестировать
def add_with_log(a: int, b: int) -> int:
    print(f"Adding {a} and {b}")  # Нужно мокировать print!
    return a + b

2. Проще параллелизировать (многопоточность)

from concurrent.futures import ThreadPoolExecutor

# Чистая функция безопасна для параллелизма
def multiply(x: int) -> int:
    return x * 2

with ThreadPoolExecutor() as executor:
    results = executor.map(multiply, [1, 2, 3, 4, 5])
    print(list(results))  # [2, 4, 6, 8, 10]

3. Кэширование результатов

from functools import lru_cache

# Чистую функцию можно кэшировать
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  # 55 (с кешированием очень быстро)
print(fibonacci(10))  # 55 (из кеша)

# Нечистую функцию кешировать опасно
def get_user_from_db(user_id: int) -> dict:
    # Если кешировать, то может вернуться старые данные!
    return db.query(f"SELECT * FROM users WHERE id = {user_id}")

Как сделать функцию чище

Правила:

  1. Передавай все необходимое как аргументы
# Плохо: зависит от config
def get_discount(price: float) -> float:
    return price * config["discount_rate"]

# Хорошо: всё передаётся
def get_discount(price: float, discount_rate: float) -> float:
    return price * discount_rate
  1. Возвращай новые объекты вместо изменения
# Плохо
def remove_duplicates(lst: list):
    lst = list(set(lst))  # Создаёт новый список
    return lst

# Хорошо
def remove_duplicates(lst: list) -> list:
    return list(set(lst))
  1. Отдели чистую логику от побочных эффектов
# Разделение ответственности
def validate_user(user: dict) -> bool:  # Чистая
    return 'email' in user and '@' in user['email']

def save_user(user: dict):  # Побочные эффекты
    if validate_user(user):
        db.insert(user)
        send_email(user['email'])

Выводы

Чистая функция должна:

  1. Быть детерминированной — одинаковый вход = одинаковый выход
  2. Не иметь побочных эффектов — не изменяет состояние вне себя
  3. Не зависеть от внешнего состояния — все необходимое передаётся как аргументы
  4. Не изменять аргументы — возвращает новые объекты
  5. Не выполнять I/O операции — не работает с файлами, БД, сетью

Чистые функции — это основа чистого, надёжного и легко тестируемого кода.