МУНИЦИПАЛЬНОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ДОПОЛНИТЕЛЬНОГО ОБРАЗОВАНИЯ
«ЦЕНТР ДЕТСКОГО ТЕХНИЧЕСКОГО ТВОРЧЕСТВА»
Методическая разработка
«Типы дескрипторов в Python»
к дополнительной общеобразовательной
общеразвивающей программе
технической направленности
«Программирование на Python»
Возраст детей: 10-17 лет
Автор: Костычев Вадим Александрович
г. Заречный Пензенской области
2026 г.
Данный материал предназначен для преподавателей информатики, программирования и студентов (учащихся), изучающих язык программирования Python. Он может быть использован как на лекциях, так и при проведении практических занятий или самостоятельной работе.
Цель: сформировать у обучающихся понимание дескриптора данных как механизма контроля над присваиванием и удалением атрибутов, а также научить правильно хранить значения атрибутов внутри экземпляра класса-владельца, избегая типичных ошибок проектирования.
Задачи:
Дать строгое определение дескриптора данных.
Разобрать сигнатуру и параметры метода __set__.
Показать, как работает __set__ при присваивании значения атрибуту.
Проанализировать три способа хранения значений в дескрипторе и их недостатки.
Научить правильно организовывать хранение данных через __init__ и уникальные имена атрибутов.
Подготовить почву для изучения метода __set_name__ как более элегантного решения.
Что такое дескриптор данных?
Дескриптор данных (data descriptor) — это объект, целью которого является контроль установки и удаления атрибутов других объектов.
Строгое определение:
Объект, в котором реализован хотя бы один из методов __set__ или __delete__, называется data-дескриптором.
Как правило, в data-дескрипторе также реализуется метод __get__, чтобы обеспечить полный цикл работы с атрибутом: чтение, запись и удаление.
💡 Ключевое отличие от non-data-дескриптора: наличие __set__ или __delete__. Именно эти методы делают дескриптор «данным» — он управляет реальным состоянием.
Метод __set__: сигнатура и параметры
Метод __set__ вызывается автоматически при присваивании значения атрибуту.
Сигнатура:
def __set__(self, instance, value) - None:
...
Параметры:
| Параметр | Что представляет |
| self | Сам экземпляр дескриптора |
| instance | Экземпляр класса-владельца, через который идёт присваивание |
| value | Значение, которое присваивается |
Пример, демонстрирующий вызов:
class CoordinateValue:
def __set__(self, instance, value):
print(f'__set__ вызван: instance={instance}, value={value}')
def __get__(self, instance, owner_class):
if instance is None:
print('__get__ вызван от класса')
else:
print(f'__get__ вызван: instance={instance}, owner={owner_class}')
class Point:
x = CoordinateValue()
y = CoordinateValue()
Point.x # __get__ вызван от класса
p = Point()
p.x # __get__ вызван: instance=, owner=
p.x = 100 # __set__ вызван: instance=, value=100
Метод __set__ срабатывает только при присваивании (p.x = 100), а не при чтении.
Главный вопрос: где хранить значение?
После вызова __set__ возникает ключевой вопрос: куда сохранить переданное значение?
Существует три основных подхода, расположенных от худшего к лучшему:
Сохранение внутри экземпляра дескриптора
Сохранение внутри экземпляра класса-владельца (с фиксированным именем)
Сохранение внутри экземпляра класса-владельца (с уникальным именем через __init__)
Разберём каждый из них.
Способ 1: Хранение внутри экземпляра дескриптора (ПЛОХО)
Идея: сохранить значение в атрибуте самого дескриптора, например _value.
class CoordinateValue:
def __set__(self, instance, value):
self._value = int(value)
def __get__(self, instance, owner_class):
if instance is None:
return self
return self._value
class Point:
x = CoordinateValue()
y = CoordinateValue()
p1 = Point()
p1.x = 100
p1.y = 200
print(p1.x, p1.y) # 100 200 ✓
p2 = Point()
print(p2.x, p2.y) # 100 200 УЖАС!
p2.x = 5
print(p1.x, p2.x) # 5 5 изменение p2 повлияло на p1!
Проблема: экземпляры дескриптора CoordinateValue() общие для всех объектов Point. Поэтому все точки делят одно и то же значение _value. Вывод: такой способ не работает — экземпляры влияют друг на друга.
Способ 2: Хранение в экземпляре класса-владельца с фиксированным именем (ПЛОХО)
Идея: сохранять значение в instance через атрибут с фиксированным именем, например _stored_value.
class CoordinateValue:
def __set__(self, instance, value):
instance._stored_value = int(value)
def __get__(self, instance, owner_class):
if instance is None:
return self
return getattr(instance, '_stored_value', None)
class Point:
x = CoordinateValue()
y = CoordinateValue()
p1 = Point()
p1.x = 77
print(p1.x, p1.y) # 77 77 оба атрибута показывают одно значение!
print(p1.__dict__) # {'_stored_value': 77}
p1.y = 5
print(p1.x, p1.y) # 5 5 y перезаписал x!
Проблема: оба дескриптора (x и y) сохраняют значение в один и тот же атрибут _stored_value. Каждое новое присваивание затирает предыдущее. Вывод: нужно уникальное имя для каждого атрибута-дескриптора.
Способ 3: Хранение с уникальным именем через __init__ (ХОРОШО)
Идея: при создании дескриптора передавать ему имя атрибута, под которым он будет хранить значение в экземпляре.
class CoordinateValue:
def __init__(self, name):
self.storage_name = '_' + name # уникальное имя для каждого дескриптора
def __set__(self, instance, value):
setattr(instance, self.storage_name, int(value))
def __get__(self, instance, owner_class):
if instance is None:
return self
return getattr(instance, self.storage_name, None)
class Point:
x = CoordinateValue('x')
y = CoordinateValue('y')
p1 = Point()
p1.x = 77
p1.y = 33
print(p1.x, p1.y) # 77 33 ✓
print(p1.__dict__) # {'_x': 77, '_y': 33}
p2 = Point()
p2.x = 2
p2.y = 3
print(p2.x, p2.y) # 2 3 ✓
print(p2.__dict__) # {'_x': 2, '_y': 3}
Преимущества:
Каждый экземпляр имеет свои собственные значения
Разные атрибуты-дескрипторы не затирают друг друга
Значения хранятся в __dict__ экземпляра класса-владельца
Недостаток: необходимость вручную передавать имя при инициализации:
x = CoordinateValue('x') # дублирование имени!
Это неудобно и подвержено ошибкам. В следующем уроке мы рассмотрим метод __set_name__, который автоматически передаёт имя атрибута в дескриптор.
Сравнительная таблица способов хранения
| Способ | Где хранится | Проблема |
| Внутри дескриптора | self._value | Все экземпляры делят одно значение |
| В экземпляре, фиксированное имя | instance._stored_value | Разные атрибуты затирают друг друга |
| В экземпляре, уникальное имя | instance._x, instance._y | Нужно вручную передавать имя |
| В экземпляре + __set_name__ | instance._x (автоматически) | Идеальный способ |
Практические рекомендации
Никогда не храните состояние в самом дескрипторе — это приведёт к общим значениям для всех экземпляров.
Всегда храните данные в экземпляре класса-владельца — это гарантирует независимость объектов.
Используйте уникальные имена для каждого атрибута-дескриптора.
Избегайте ручного указания имён — переходите к __set_name__ как можно скорее.
Используйте setattr/getattr для работы с динамическими именами атрибутов.
Заключение
Data-дескриптор — объект с методами __set__ и/или __delete__.
Метод __set__(self, instance, value) вызывается при присваивании атрибуту.
Хранить значения внутри дескриптора нельзя — экземпляры будут влиять друг на друга.
Хранить значения в экземпляре класса-владельца — правильный подход.
Для разных атрибутов нужны уникальные имена в __dict__.
Передача имени через __init__ решает проблему, но неудобна.
Следующий шаг — использование __set_name__ для автоматического получения имени.
Освоив дескриптор данных, вы получаете точный инструмент управления атрибутами с полным контролем над их установкой. Понимание того, где и как хранить значения, — ключ к созданию надёжных и предсказуемых дескрипторов.