Добавить в корзинуПозвонить
Найти в Дзене

Продвинутый лайфхак: Оптимизация кода с помощью декораторов, генераторов и контекстных менеджеров в Python

Если ты уже освоил основы Python и хочешь вывести свои навыки на новый уровень, этот лайфхак поможет тебе писать более эффективный, читаемый и профессиональный код. Мы разберём, как комбинировать декораторы, генераторы и контекстные менеджеры для решения сложных задач. 1. Декораторы для кэширования и логирования Декораторы — это мощный инструмент для модификации функций без изменения их кода. Давай создадим декоратор, который кэширует результаты и логирует время выполнения. import functools
import time
import logging
logging.basicConfig(level=logging.INFO)
def cache_and_log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Кэширование результата
cache_key = str(args) + str(kwargs)
if not hasattr(wrapper, 'cache'):
wrapper.cache = {}
if cache_key in wrapper.cache:
logging.info(f"Результат для {func.__name__} взят из кэша")
return wrapper.cache[cache_key]
# Логирование времени выпол

Если ты уже освоил основы Python и хочешь вывести свои навыки на новый уровень, этот лайфхак поможет тебе писать более эффективный, читаемый и профессиональный код. Мы разберём, как комбинировать декораторы, генераторы и контекстные менеджеры для решения сложных задач.

1. Декораторы для кэширования и логирования

Декораторы — это мощный инструмент для модификации функций без изменения их кода. Давай создадим декоратор, который кэширует результаты и логирует время выполнения.

import functools
import time
import logging

logging.basicConfig(level=logging.INFO)

def cache_and_log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Кэширование результата
cache_key = str(args) + str(kwargs)
if not hasattr(wrapper, 'cache'):
wrapper.cache = {}
if cache_key in wrapper.cache:
logging.info(f"Результат для {func.__name__} взят из кэша")
return wrapper.cache[cache_key]

# Логирование времени выполнения
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
logging.info(f"{func.__name__} выполнена за {end_time - start_time:.4f} сек")

wrapper.cache[cache_key] = result
return result
return wrapper

@cache_and_log
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(35)) # Первая попытка
print(fibonacci(35)) # Результат из кэша

Почему это круто?

  • Кэш избавляет от повторных вычислений (особенно полезно для рекурсии).
  • Логирование помогает отлаживать производительность.
  • @functools.wraps сохраняет метаданные функции, что важно для профессионального кода.

2. Генераторы для экономии памяти

Когда работаешь с большими данными, списки могут "съесть" всю оперативку. Генераторы решают эту проблему, выдавая элементы по одному.

def large_data_generator(start, end):
for i in range(start, end):
yield i ** 2 # Генерируем квадраты чисел по одному

# Используем генератор в цикле
for num in large_data_generator(1, 1000000):
if num > 100:
print(f"Первое число > 100: {num}")
break

Лайфхак: Комбинируй генераторы с itertools для ещё большей гибкости. Например, itertools.islice позволяет брать только часть данных без загрузки всего в память:

from itertools import islice
first_10 = list(islice(large_data_generator(1, 1000000), 10))
print(first_10)

3. Контекстные менеджеры для управления ресурсами

Контекстные менеджеры (с ключевым словом with) упрощают работу с файлами, соединениями и другими ресурсами. Создадим свой менеджер для временного изменения настроек.

from contextlib import contextmanager

@contextmanager
def temp_config(config_dict, key, value):
old_value = config_dict.get(key)
config_dict[key] = value
try:
yield
finally:
if old_value is None:
del config_dict[key]
else:
config_dict[key] = old_value

# Пример использования
settings = {"mode": "normal"}
print(f"До: {settings}")

with temp_config(settings, "mode", "debug"):
print(f"Во время: {settings}")

print(f"После: {settings}")

Вывод:

До: {'mode': 'normal'}
Во время: {'mode': 'debug'}
После: {'mode': 'normal'}

Почему это полезно?

  • Гарантированно возвращает исходное состояние, даже если внутри блока with произойдёт ошибка.
  • Удобно для тестирования или временных изменений.

4. Комбинируем всё вместе

Теперь объединим эти техники в одном примере — обработка большого лога с кэшированием и временным изменением настроек.

import logging
from contextlib import contextmanager
from itertools import islice

logging.basicConfig(level=logging.INFO)

@contextmanager
def log_level(level):
old_level = logging.getLogger().level
logging.getLogger().setLevel(level)
try:
yield
finally:
logging.getLogger().setLevel(old_level)

def log_generator(file_path):
with open(file_path, 'r') as f:
for line in f:
yield line.strip()

@cache_and_log
def count_errors(log_lines):
return sum(1 for line in log_lines if "ERROR" in line)

# Пример использования
log_file = "sample.log" # Предположим, у нас есть файл логов
with log_level(logging.DEBUG):
logs = log_generator(log_file)
error_count = count_errors(islice(logs, 1000)) # Обрабатываем только первые 1000 строк
print(f"Найдено ошибок: {error_count}")

Что мы получили?

  • Генератор читает файл построчно, экономя память.
  • Декоратор кэширует результат и логирует время.
  • Контекстный менеджер временно меняет уровень логирования.

Заключение

Эти техники — не просто "фишки для резюме". Они реально ускоряют код, упрощают отладку и делают его масштабируемым. Экспериментируй с ними в своих проектах, добавляй свои идеи и делись результатами!