Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Критерии чистой функции (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}")
Как сделать функцию чище
Правила:
- Передавай все необходимое как аргументы
# Плохо: зависит от 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
- Возвращай новые объекты вместо изменения
# Плохо
def remove_duplicates(lst: list):
lst = list(set(lst)) # Создаёт новый список
return lst
# Хорошо
def remove_duplicates(lst: list) -> list:
return list(set(lst))
- Отдели чистую логику от побочных эффектов
# Разделение ответственности
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'])
Выводы
Чистая функция должна:
- Быть детерминированной — одинаковый вход = одинаковый выход
- Не иметь побочных эффектов — не изменяет состояние вне себя
- Не зависеть от внешнего состояния — все необходимое передаётся как аргументы
- Не изменять аргументы — возвращает новые объекты
- Не выполнять I/O операции — не работает с файлами, БД, сетью
Чистые функции — это основа чистого, надёжного и легко тестируемого кода.