Чистая функция зависит только от аргументов, не меняет внешнее состояние и при одинаковом вводе возвращает одинаковый результат.
Что такое Pure Function?
Pure function (чистая функция) — это функция, которая:
1. Всегда возвращает одинаковый результат при одинаковых аргументах
2. Не имеет побочных эффектов — не изменяет внешнее состояние
# Чистая функция
def add(a, b):
return a + b
print(add(2, 3)) # 5
print(add(2, 3)) # 5 — всегда одинаково
# Нечистая функция
total = 0
def add_impure(x):
global total
total += x # меняет внешнее состояние!
return total
print(add_impure(5)) # 5
print(add_impure(5)) # 10 — разный результат!
Примеры чистых функций
# Математика — чисто
def square(x):
return x ** 2
def average(numbers):
return sum(numbers) / len(numbers)
def distance(x1, y1, x2, y2):
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
# Строки — чисто (исходная строка не меняется)
def uppercase(text):
return text.upper()
def count_vowels(text):
vowels = "aeiouаеёиоуыэюя"
return sum(1 for char in text.lower() if char in vowels)
# Списки — чисто (возвращает НОВЫЙ список)
def double_all(numbers):
return [x * 2 for x in numbers]
def filter_positive(numbers):
return [x for x in numbers if x > 0]
Примеры нечистых функций
# 1. Изменение глобального состояния
total_score = 0
def add_score_impure(points): # ❌ нечистая
global total_score
total_score += points
return total_score
def add_score_pure(current_total, points): # ✅ чистая
return current_total + points
# 2. Изменение аргументов
def append_impure(lst, item): # ❌ меняет оригинальный список!
lst.append(item)
return lst
def append_pure(lst, item): # ✅ возвращает новый список
return lst + [item]
# 3. Побочные эффекты (I/O)
def print_result_impure(x): # ❌ print — это side effect
print(f"Result: {x}")
return x
def format_result_pure(x): # ✅ возвращает строку — вывод снаружи
return f"Result: {x}"
# 4. Случайность
import random
def get_random_impure(): # ❌ разный результат каждый раз
return random.randint(1, 100)
def get_random_pure(seed): # ✅ одинаковый результат при том же seed
random.seed(seed)
return random.randint(1, 100)
Почему Pure Functions хороши?
Предсказуемость и тестируемость
def add(a, b):
return a + b
# Тест — просто и надёжно
def test_add():
assert add(2, 3) == 5
assert add(0, 0) == 0
assert add(-1, 1) == 0
# Работает всегда одинаково, без setUp/tearDown
Кэширование (memoization)
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
"""Чистая функция — безопасно кэшировать."""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # вычисляется
print(fibonacci(100)) # берётся из кэша мгновенно
Безопасный параллелизм
from multiprocessing import Pool
def square(x):
return x ** 2
with Pool(4) as pool:
results = pool.map(square, [1, 2, 3, 4, 5])
print(results) # [1, 4, 9, 16, 25]
# Нет общего состояния — нет гонок данных
Как сделать функцию чистой?
Проблема: глобальная переменная → передать аргументом
# ❌ нечистая
score = 0
def add_points_impure(points):
global score
score += points
return score
# ✅ чистая
def add_points_pure(current_score, points):
return current_score + points
score = 0
score = add_points_pure(score, 10) # 10
score = add_points_pure(score, 5) # 15
Проблема: изменение коллекции → вернуть новую
# ❌ нечистая — меняет оригинал
def sort_impure(numbers):
numbers.sort()
return numbers
# ✅ чистая — возвращает новый список
def sort_pure(numbers):
return sorted(numbers)
original = [3, 1, 2]
sorted_list = sort_pure(original)
print(original) # [3, 1, 2] ← не изменился
print(sorted_list) # [1, 2, 3]
# ❌ нечистая — меняет словарь
def update_age_impure(person, new_age):
person["age"] = new_age
return person
# ✅ чистая — возвращает новый словарь
def update_age_pure(person, new_age):
return {**person, "age": new_age}
person = {"name": "Алиса", "age": 25}
updated = update_age_pure(person, 26)
print(person) # {'name': 'Алиса', 'age': 25} ← не изменился
print(updated) # {'name': 'Алиса', 'age': 26}
Практический пример: обработка данных студентов
def get_high_performers(students, threshold=90):
"""Чистая: фильтрация по оценке."""
return [s for s in students if s["grade"] >= threshold]
def add_status(students):
"""Чистая: добавляет поле без изменения оригинала."""
return [
{**s, "status": "Отличник" if s["grade"] >= 90 else "Хорошист"}
for s in students
]
students = [
{"name": "Алиса", "grade": 95},
{"name": "Боб", "grade": 87},
{"name": "Карл", "grade": 92},
]
high_performers = get_high_performers(students, 90)
with_status = add_status(students)
print(high_performers)
# [{'name': 'Алиса', 'grade': 95}, {'name': 'Карл', 'grade': 92}]
print(students[0])
# {'name': 'Алиса', 'grade': 95} ← поле 'status' не добавлено в оригинал
Частые ошибки
Ошибка 1: Изменение аргумента напрямую
# ❌ мутирует список — вызывающий код не ожидает этого
def add_item_impure(items, new_item):
items.append(new_item)
return items
# ✅ создать новый список
def add_item_pure(items, new_item):
return items + [new_item]
Ошибка 2: Скрытые зависимости от времени или случайности
import time, random
# ❌ нечистые — результат непредсказуем
def get_timestamp():
return time.time()
def shuffle_data(items):
random.shuffle(items) # ещё и мутирует!
return items
# ✅ принять значение аргументом
def format_timestamp(timestamp):
return time.strftime("%Y-%m-%d", time.localtime(timestamp))
Ключевое правило: передавайте состояние через аргументы, возвращайте новые данные, не изменяйте старые. Нечистые операции (I/O, БД, сеть) изолируйте в отдельных функциях.
💬 Комментарии (0)
Комментариев пока нет
Станьте первым, кто поделится мнением об этой статье!