L o a d i n g
Продвинутый лайфхак: Оптимизация кода с помощью декораторов, генераторов и контекстных менеджеров в Python 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]
        
        # Логирование времени выполнения
        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}")

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

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

Заключение

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

Написать комментарий

Вы можете оставить комментарий автору статьи Обязательные поля помечены *