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

Какие знаешь варианты lazy loading?

2.0 Middle🔥 161 комментариев
#Python Core

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

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

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

Варианты lazy loading в Python

Lazy loading — это паттерн, когда данные или ресурсы загружаются только когда они действительно нужны, а не заранее. Это экономит память и ускоряет запуск программы.

1. Property с вычислением на лету

class User:
    def __init__(self, user_id):
        self.user_id = user_id
        self._profile = None  # Кэш
    
    @property
    def profile(self):
        # Загружаем только когда обращаются
        if self._profile is None:
            print(f"Загружаю профиль для user {self.user_id}")
            self._profile = self._fetch_profile_from_db()
        return self._profile
    
    def _fetch_profile_from_db(self):
        # Имитация запроса к БД
        return {'id': self.user_id, 'name': 'John', 'bio': 'Hello'}

# Использование
user = User(1)
print(user.user_id)  # Быстро, без загрузки
print(user.profile)  # [Загружаю...] Вот здесь загружается
print(user.profile)  # Быстро, из кэша

2. Generator с yield

Отлично для больших последовательностей данных.

def lazy_range(n):
    """Генератор вместо list()"""
    for i in range(n):
        print(f"Генерирую {i}")
        yield i

# Ленивое вычисление
for i in lazy_range(5):
    if i == 2:
        break  # Сгенерируется только 0, 1, 2

# vs
for i in range(5):  # Создаёт весь список в памяти
    if i == 2:
        break

# Со списком:
data = list(range(1000000))  # Создаёт 1M элементов в памяти = ~40 MB

# С генератором:
data = (x for x in range(1000000))  # Генерирует по одному = ~0 KB

Практический пример: загрузка больших файлов

def lazy_read_large_file(filepath, chunk_size=1024):
    """Читаем файл по частям, не всё целиком в память"""
    with open(filepath, 'r') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            yield chunk

# Использование
for chunk in lazy_read_large_file('huge_file.txt'):
    process(chunk)  # Обрабатываем по частям, не 10 GB в памяти

3. Lazy evaluation с itertools

import itertools

# Плохо: создаёт весь список
result = [x*2 for x in range(1000000) if x % 2 == 0]
# Память: ~20 MB

# Хорошо: ленивое вычисление
result = (x*2 for x in range(1000000) if x % 2 == 0)
# Память: ~0 KB

# Ещё лучше с itertools
result = itertools.islice(
    map(lambda x: x*2, filter(lambda x: x % 2 == 0, range(1000000))),
    0, 10  # Беру только первые 10
)
# Вычисляется только 10 элементов!
for item in result:
    print(item)  # 0, 4, 8, 12, ...

4. Lazy evaluation в ORM (SQLAlchemy, Django ORM)

SQLAlchemy

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

Session = sessionmaker(bind=engine)
session = Session()

# Ленивая загрузка (lazy loading)
users_query = session.query(User)  # Ещё БД не запрашивалась!
print("Запрос создан, но БД не загружена")

for user in users_query:  # Вот здесь выполняется SQL
    print(user.name)

# Или с limit
first_10 = session.query(User).limit(10)  # Ещё не выполняется
users = list(first_10)  # Вот здесь SQL: SELECT * FROM users LIMIT 10

Django ORM

# QuerySet ленивый по умолчанию
users = User.objects.all()  # Нет SQL запроса!
print("Запрос создан, но БД не загружена")

for user in users:  # Вот здесь выполняется SQL
    print(user.name)

# Оценить без загрузки
count = users.count()  # SELECT COUNT(*) — другой запрос!

# Принудительная загрузка (eager loading)
users_eager = list(User.objects.all())  # Вот здесь выполняется SELECT *

5. Lazy initialization в конструкторе

class DatabaseConnection:
    def __init__(self, config):
        self.config = config
        self._connection = None  # Ленивая инициализация
    
    @property
    def connection(self):
        if self._connection is None:
            print(f"Подключаюсь к {self.config['host']}...")
            self._connection = self._create_connection()
        return self._connection
    
    def _create_connection(self):
        # Имитация подключения (дорогая операция)
        import time
        time.sleep(1)  # Долгое подключение
        return f"Connected to {self.config['host']}"

# Использование
db = DatabaseConnection({'host': 'localhost'})
print("База данных инициализирована")
# Нет подключения!

print(db.connection)  # [Подключаюсь...] Вот здесь подключаемся
print(db.connection)  # Быстро, из кэша

6. getattr для динамической загрузки

class LazyObject:
    def __init__(self, module_name):
        self.module_name = module_name
        self._module = None
    
    def __getattr__(self, name):
        if self._module is None:
            print(f"Импортирую {self.module_name}...")
            self._module = __import__(self.module_name)
        return getattr(self._module, name)

# Использование
math_lazy = LazyObject('math')
print("Объект создан, но модуль не загружен")
print(math_lazy.pi)  # [Импортирую math...] Вот здесь
print(math_lazy.sqrt(16))  # Быстро, модуль уже в памяти

7. functools.lru_cache с lazy evaluation

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_computation(n):
    """Вычисляется один раз, потом кэшируется"""
    print(f"Вычисляю для {n}...")
    return sum(i*i for i in range(n))

print(expensive_computation(1000))  # [Вычисляю...] Долгое
print(expensive_computation(1000))  # Быстро из кэша
print(expensive_computation(2000))  # [Вычисляю...] Новый параметр

8. Lazy import в модулях

# Плохо: импортируешь всё сразу
import numpy as np
import pandas as pd
import tensorflow as tf
import scipy
# Все библиотеки загружаются, даже если не нужны = медленный старт

# Хорошо: импортируешь только когда нужно
def process_with_numpy():
    import numpy as np  # Импортируется только если функция вызывается
    return np.array([1, 2, 3])

def process_with_pandas():
    import pandas as pd
    return pd.DataFrame()

# Программа стартует быстро, импорты происходят по необходимости

9. Async/await для I/O lazy loading

import asyncio

async def fetch_user_lazy(user_id):
    """Загружаем асинхронно, не блокируя"""
    print(f"Начинаю загружать user {user_id}...")
    await asyncio.sleep(1)  # Имитация сетевой задержки
    print(f"Загружен user {user_id}")
    return {'id': user_id, 'name': f'User {user_id}'}

async def main():
    # Создаём задачи без ожидания
    tasks = [
        fetch_user_lazy(1),
        fetch_user_lazy(2),
        fetch_user_lazy(3),
    ]
    # Загружаются параллельно, не последовательно
    results = await asyncio.gather(*tasks)
    print(results)

# Запуск
asyncio.run(main())

Сравнение: Когда использовать?

ТипИспользованиеПримеры
PropertyВычисления по требованиюПрофиль пользователя, конфиг
GeneratorБольшие последовательностиЧтение файлов, потоки данных
ORM QueryЛенивые БД запросыSQLAlchemy, Django
getattrДинамическая загрузкаПлагины, модули
lru_cacheМемоизация дорогих функцийВычисления, API запросы
Lazy importУскорение старта программыТяжёлые библиотеки
async/awaitПараллельный I/OСетевые запросы

На собеседовании

"Lazy loading — это загрузка данных только когда они нужны. В Python это реализуется через properties (кэширование), generators (экономия памяти), ленивые QuerySet в ORM, и async/await для параллельного I/O. Например, если открыть 1 млн строк файла — лучше использовать generator и читать по чанкам, чем загружать всё в память сразу."

Какие знаешь варианты lazy loading? | PrepBro