Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Flyweight и __slots__
flyweight

Flyweight и __slots__

Оптимизация памяти через общие объекты. __slots__ для экономии памяти. Interning строк и кэширование мелких объектов.

Flyweight и slots

Экономия памяти — это не оптимизация, это архитектура.


#Flyweight: экономия через общие объекты

Flyweight — структурный паттерн для экономии памяти через переиспользование общих объектов вместо создания новых.

#Проблема: много одинаковых объектов

# ❌ ПЛОХО: Много памяти на одинаковые данные class Character: def __init__(self, char: str, font: str, size: int, color: str): self.char = char # Уникальное self.font = font # Общее для многих self.size = size # Общее для многих self.color = color # Общее для многих # Текст из 10000 символов одним шрифтом text = "Hello World!" * 1000 characters = [Character(c, "Arial", 12, "black") for c in text] # 10000 объектов, каждый хранит "Arial", 12, "black" # Это ~400KB только на строки "Arial" и "black"!

#Решение: разделение на intrinsic и extrinsic

# ✅ ХОРОШО: Общие данные хранятся один раз class CharacterFont: """Flyweight — общие данные""" def __init__(self, font: str, size: int, color: str): self.font = font self.size = size self.color = color class Character: """Контекст — уникальные данные""" def __init__(self, char: str, font: CharacterFont): self.char = char # Уникальное self.font = font # Ссылка на общий объект # Фабрика flyweight-объектов class FontFactory: def __init__(self): self._fonts = {} def get_font(self, font: str, size: int, color: str) -> CharacterFont: key = (font, size, color) if key not in self._fonts: self._fonts[key] = CharacterFont(font, size, color) print(f"Created new font: {key}") else: print(f"Reusing font: {key}") return self._fonts[key] # Использование factory = FontFactory() text = "Hello World!" * 1000 characters = [] for c in text: font = factory.get_font("Arial", 12, "black") characters.append(Character(c, font)) # Created new font: ('Arial', 12, 'black') — только один раз! # Все 10000 символов ссылаются на один объект CharacterFont

#slots: экономия памяти на экземплярах

#Проблема: dict для каждого объекта

import sys class Point: def __init__(self, x, y): self.x = x self.y = y # Каждый экземпляр имеет __dict__ p = Point(1, 2) print(sys.getsizeof(p.__dict__)) # ~232 bytes на словарь print(sys.getsizeof(p)) # ~56 bytes сам объект # 1000000 точек = ~300MB только на __dict__!

#Решение: slots

import sys class Point: __slots__ = ['x', 'y'] # Фиксированный набор атрибутов def __init__(self, x, y): self.x = x self.y = y p = Point(1, 2) # print(p.__dict__) # AttributeError! __dict__ нет print(sys.getsizeof(p)) # ~48 bytes — экономия! # 1000000 точек = ~48MB вместо ~300MB

Как работает: __slots__ заменяет __dict__ на фиксированный массив атрибутов. Это экономит память, но запрещает динамические атрибуты.

#Сравнение памяти

import sys class WithoutSlots: def __init__(self, a, b, c): self.a = a self.b = b self.c = c class WithSlots: __slots__ = ['a', 'b', 'c'] def __init__(self, a, b, c): self.a = a self.b = b self.c = c # Создаём по 100000 объектов objs1 = [WithoutSlots(1, 2, 3) for _ in range(100000)] objs2 = [WithSlots(1, 2, 3) for _ in range(100000)] # Без __slots__: ~32MB # С __slots__: ~12MB # Экономия: ~60%!

#Interning: кэширование неизменяемых объектов

#Строки

# Python автоматически интернирует некоторые строки a = "hello" b = "hello" print(a is b) # True — один объект! # Но не все: c = "".join(["h", "e", "l", "l", "o"]) print(a is c) # Может быть False # Явная интернация import sys d = sys.intern("".join(["h", "e", "l", "l", "o"])) print(a is d) # True — гарантированно один объект

#Целые числа

# Python кэшиет малые целые (-5 до 256) a = 256 b = 256 print(a is b) # True a = 257 b = 257 print(a is b) # False (в общем случае) # Но в одном выражении может быть True из-за оптимизации компилятора

#Практическое применение

#Кэш объектов

from typing import Dict, Tuple class Color: """Flyweight для цветов""" _cache: Dict[Tuple[int, int, int], "Color"] = {} def __new__(cls, r: int, g: int, b: int): key = (r, g, b) if key not in cls._cache: instance = super().__new__(cls) cls._cache[key] = instance print(f"Created Color({r}, {g}, {b})") else: print(f"Reusing Color({r}, {g}, {b})") return cls._cache[key] def __init__(self, r: int, g: int, b: int): # __init__ вызывается каждый раз, но это ок self.r = r self.g = g self.b = b # Использование red1 = Color(255, 0, 0) # Created Color(255, 0, 0) red2 = Color(255, 0, 0) # Reusing Color(255, 0, 0) print(red1 is red2) # True

#Flyweight для игровых объектов

from typing import Dict, Tuple class TreeType: """Flyweight — тип дерева""" def __init__(self, name: str, color: str, texture: str): self.name = name self.color = color self.texture = texture class Tree: """Дерево в лесу""" def __init__(self, x: int, y: int, tree_type: TreeType): self.x = x self.y = y self.tree_type = tree_type class ForestFactory: """Фабрика типов деревьев""" def __init__(self): self._types: Dict[str, TreeType] = {} def get_type(self, name: str, color: str, texture: str) -> TreeType: key = (name, color, texture) if key not in self._types: self._types[key] = TreeType(name, color, texture) return self._types[key] # Использование factory = ForestFactory() # 10000 деревьев, но только 3 типа trees = [] for i in range(10000): if i % 3 == 0: tree_type = factory.get_type("Oak", "green", "oak_bark.png") elif i % 3 == 1: tree_type = factory.get_type("Pine", "dark_green", "pine_bark.png") else: tree_type = factory.get_type("Birch", "white", "birch_bark.png") trees.append(Tree(i, i, tree_type)) # Только 3 объекта TreeType вместо 10000!

#slots для dataclass

from dataclasses import dataclass @dataclass class Point: x: float y: float z: float @dataclass class PointSlots: __slots__ = ['x', 'y', 'z'] x: float y: float z: float # PointSlots использует ~40% меньше памяти

#Когда НЕ использовать

#slots для простых случаев

# ❌ ПЛОХО: __slots__ без необходимости class Config: __slots__ = ['debug', 'timeout'] def __init__(self): self.debug = False self.timeout = 30 # Если объектов мало — __slots__ избыточен # Используйте обычный класс

#slots при необходимости динамических атрибутов

# ❌ ПЛОХО: __slots__ запрещает динамические атрибуты class Dynamic: __slots__ = ['fixed'] def __init__(self): self.fixed = 1 d = Dynamic() d.dynamic = 2 # AttributeError!

#Flyweight для уникальных объектов

# ❌ ПЛОХО: Flyweight когда все объекты разные class User: _cache = {} def __new__(cls, name, email, age, address, phone): # Каждый пользователь уникален — кэш бесполезен key = (name, email, age, address, phone) ...

#Производительность

#Сравнение Flyweight

import time # Без Flyweight class WithoutFlyweight: def __init__(self, font, size, color): self.font = font self.size = size self.color = color start = time.time() objs = [WithoutFlyweight("Arial", 12, "black") for _ in range(100000)] print(f"Without: {time.time() - start:.3f}s") # С Flyweight class WithFlyweight: _cache = {} def __init__(self, font, size, color): key = (font, size, color) if key not in self._cache: self._cache[key] = (font, size, color) self.data = self._cache[key] start = time.time() objs = [WithFlyweight("Arial", 12, "black") for _ in range(100000)] print(f"With: {time.time() - start:.3f}s") # С Flyweight быстрее и меньше памяти

#Что дальше?

Изучите тему Proxy и Lazy Loading для контроля доступа к объектам.

Далее: Proxy и Lazy Loading