МУНИЦИПАЛЬНОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ДОПОЛНИТЕЛЬНОГО ОБРАЗОВАНИЯ
«ЦЕНТР ДЕТСКОГО ТЕХНИЧЕСКОГО ТВОРЧЕСТВА»
Методическая разработка
«__slots__ в Python»
к дополнительной общеобразовательной
общеразвивающей программе
технической направленности
«Программирование на Python»
Возраст детей: 10-17 лет
Автор: Костычев Вадим Александрович
г. Заречный Пензенской области
2026 г.
Данный материал предназначен для преподавателей информатики, программирования и студентов (учащихся), изучающих язык программирования Python. Он может быть использован как на лекциях, так и при проведении практических занятий или самостоятельной работе.
Цель: сформировать у учащихся понимание механизма __slots__ как инструмента оптимизации памяти и контроля над атрибутами экземпляров класса, а также научить применять его в практических задачах.
Задачи:
Объяснить, как Python хранит атрибуты объектов по умолчанию (через __dict__).
Показать проблемы, связанные с использованием __dict__ (избыточное потребление памяти, возможность добавления произвольных атрибутов).
Ввести механизм __slots__ как альтернативу __dict__.
Научить объявлять и использовать __slots__ в классах.
Разобрать ограничения и особенности применения __slots__.
Как Python хранит атрибуты по умолчанию?
По умолчанию каждый экземпляр класса в Python имеет специальный атрибут __dict__ — это словарь, в котором хранятся все пользовательские атрибуты объекта.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.__dict__) # {'x': 1, 'y': 2}
Это удобно: можно динамически добавлять новые атрибуты:
p.z = 3
print(p.__dict__) # {'x': 1, 'y': 2, 'z': 3}
Но есть два недостатка:
1. Избыточное потребление памяти: словарь занимает много места, особенно если объектов много.
2. Отсутствие контроля: можно случайно создать атрибут с опечаткой (p.x_coor вместо p.x_coord), и ошибка не будет замечена.
Что такое __slots__?
Механизм __slots__ позволяет заменить __dict__ на фиксированный набор атрибутов, заданных при определении класса.
Синтаксис:
class ClassName:
__slots__ = ('attr1', 'attr2', ...)
Пример:
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x, p.y) # 1 2
Теперь объект не имеет __dict__:
print(p.__dict__) # AttributeError: 'Point' object has no attribute '__dict__'
И нельзя добавить новый атрибут:
p.z = 3 # AttributeError: 'Point' object has no attribute 'z'
Преимущества __slots__
Экономия памяти
Объекты со __slots__ занимают значительно меньше памяти, потому что:
- Нет словаря __dict__
- Атрибуты хранятся в структуре фиксированного размера (аналог массива)
Пример сравнения (при большом количестве объектов):
import sys
class PointDict:
def __init__(self, x, y):
self.x = x
self.y = y
class PointSlots:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p1 = PointDict(1, 2)
p2 = PointSlots(1, 2)
print(sys.getsizeof(p1)) # ~56 байт (включая __dict__)
print(sys.getsizeof(p2)) # ~48 байт (только атрибуты)
Разница становится критичной при создании тысяч и миллионов объектов.
2. Защита от ошибок
Запрет на динамическое добавление атрибутов помогает ловить опечатки:
p = PointSlots(1, 2)
p.x_coor = 5 # AttributeError → сразу видна ошибка!
3. Ускорение доступа к атрибутам
Доступ к атрибутам через __slots__ немного быстрее, чем через __dict__, потому что используется прямой доступ по индексу.
Как правильно использовать __slots__?
Базовое использование: укажите кортеж или список строк с именами разрешённых атрибутов:
class Rectangle:
__slots__ = ('width', 'height')
def __init__(self, width, height):
self.width = width
self.height = height
Наследование и __slots__: если родительский класс использует __slots__, дочерний класс должен объявить свои собственные слоты:
class ColoredRectangle(Rectangle):
__slots__ = ('color',) # обязательно! Иначе появится __dict__
def __init__(self, width, height, color):
super().__init__(width, height)
self.color = color
Если дочерний класс не объявляет __slots__, он автоматически получит __dict__, и преимущества будут потеряны.
Пустой __slots__: можно указать пустой кортеж, если объект не должен иметь никаких атрибутов:
class Empty:
__slots__ = ()
Ограничения __slots__
| Ограничение | Последствие |
| Нет __dict__ | Невозможно добавлять атрибуты динамически |
| Нет __weakref__ | Объекты нельзя использовать в weakref, если не добавить '__weakref__' в __slots__ |
| Проблемы с множественным наследованием | Если два родителя имеют __slots__, может возникнуть конфликт |
| Не работает с __getattr__/__setattr__ без осторожности | Требуется дополнительная обработка |
Пример для weakref:
import weakref
class SafeClass:
__slots__ = ('value', '__weakref__') # ← добавили __weakref__
def __init__(self, value):
self.value = value
obj = SafeClass(42)
ref = weakref.ref(obj) # Теперь работает
Когда использовать __slots__?
__slots__ используется, когда:
- Создаётся много однотипных объектов (точки, векторы, записи в базе данных);
- Нужна оптимизация памяти;
- Требуется строгий контроль над атрибутами;
- Класс представляет простую структуру данных/
Не использовать, когда:
- Нужна гибкость (динамические атрибуты);
- Класс предназначен для расширения пользователями;
- Работаете с множественным наследованием от нескольких классов со __slots__, если не уверены в совместимости;
- Планируете использовать сериализацию через pickle без дополнительной настройки.
Практический пример: оптимизация большого количества объектов
Представим, что вы разрабатываете игру с тысячами частиц:
# Без __slots__
class Particle:
def __init__(self, x, y, vx, vy):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
# Со __slots__
class OptimizedParticle:
__slots__ = ('x', 'y', 'vx', 'vy')
def __init__(self, x, y, vx, vy):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
При создании 1 000 000 объектов разница в потреблении памяти может составлять сотни мегабайт. В ресурсоёмких приложениях это критично.
Важные нюансы
__slots__ не наследуется автоматически
Если родительский класс имеет __slots__, дочерний класс не унаследует их список — он должен объявить свои слоты явно. Однако атрибуты из родительских слотов остаются доступными.
class A:
__slots__ = ('x',)
class B(A):
__slots__ = ('y',) # не забудьте!
b = B()
b.x = 1 # OK — унаследован от A
b.y = 2 # OK — свой слот
b.z = 3 # Ошибка!
__slots__ должен быть статическим атрибутом
Он задаётся на уровне класса, а не в __init__:
# Правильно
class Point:
__slots__ = ('x', 'y')
# Неправильно (не сработает)
class BadPoint:
def __init__(self):
self.__slots__ = ('x', 'y') # ← это просто обычный атрибут!
Совместимость с dataclasses и namedtuple
В современном Python часто вместо ручного __slots__ используют:
- @dataclass(slots=True) — начиная с Python 3.10
- collections.namedtuple — для неизменяемых структур
Пример с dataclass:
from dataclasses import dataclass
@dataclass(slots=True)
class Point:
x: float
y: float
Это делает код ещё короче и чище.
Заключение
По умолчанию атрибуты объектов хранятся в __dict__ — это гибко, но неэффективно по памяти.
- __slots__ заменяет __dict__ на фиксированный набор атрибутов.
- Преимущества: экономия памяти, защита от ошибок, небольшой прирост скорости.
- Недостатки: потеря гибкости, сложности при наследовании.
- Используйте __slots__, когда создаёте много однотипных объектов и знаете все их атрибуты заранее.
- Не используйте __slots__ в общих, расширяемых или пользовательских классах.
Освоив __slots__, вы получите инструмент для написания эффективного, надёжного и профессионального кода — особенно в задачах, где важны производительность и контроль над данными.
Проверь себя
1. Что хранит __dict__ и почему он «дорогой»?
2. Как объявить __slots__ для класса с атрибутами name и age?
3. Можно ли добавить новый атрибут к объекту со __slots__? Почему?
4. Что произойдёт, если дочерний класс не объявит __slots__, а родитель — объявит?
5. Как добавить поддержку weakref к классу со __slots__?
6. В каких случаях __slots__ даёт наибольший выигрыш?
7. Чем @dataclass(slots=True) отличается от ручного __slots__?