Комментарии (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 и читать по чанкам, чем загружать всё в память сразу."