Оптимизация памяти через общие объекты. __slots__ для экономии памяти. Interning строк и кэширование мелких объектов.
Экономия памяти — это не оптимизация, это архитектура.
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"!# ✅ ХОРОШО: Общие данные хранятся один раз
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 символов ссылаются на один объект CharacterFontimport 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__!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%!# 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) # Truefrom 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!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__ без необходимости
class Config:
__slots__ = ['debug', 'timeout']
def __init__(self):
self.debug = False
self.timeout = 30
# Если объектов мало — __slots__ избыточен
# Используйте обычный класс# ❌ ПЛОХО: __slots__ запрещает динамические атрибуты
class Dynamic:
__slots__ = ['fixed']
def __init__(self):
self.fixed = 1
d = Dynamic()
d.dynamic = 2 # AttributeError!# ❌ ПЛОХО: Flyweight когда все объекты разные
class User:
_cache = {}
def __new__(cls, name, email, age, address, phone):
# Каждый пользователь уникален — кэш бесполезен
key = (name, email, age, address, phone)
...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 быстрее и меньше памяти| Паттерн | Когда использовать | Экономия |
|---|---|---|
| Flyweight | Много объектов с общими данными | До 90% памяти |
| slots | Много объектов с фиксированными атрибутами | До 60% памяти |
| Interning | Неизменяемые строки/числа | Зависит от данных |
Главный принцип: Храните общие данные один раз, уникальные — в каждом объекте.
Изучите тему Proxy и Lazy Loading для контроля доступа к объектам.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.