Дескрипторы: __get__, __set__, __delete__
Реализуйте протокол дескрипторов для управления доступом к атрибутам.
«Дескрипторы: __get__, __set__, __delete__» — бесплатный урок Python Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Python Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Python Academy содержит 4 уроков всего.
Что такое дескриптор
Дескриптор — это объект, определяющий __get__, __set__ или __delete__ и назначенный атрибутом класса. Python вызывает эти методы при обращении к атрибуту.
class Descriptor:
def __get__(self, obj, objtype=None):
print(f"__get__ called, obj={obj}")
return 42
class MyClass:
attr = Descriptor()
print(MyClass().attr) # __get__ called ... 42Дескрипторы данных и недескрипторы данных
Дескрипторы данных определяют __set__ или __delete__ и имеют приоритет над __dict__ экземпляра. Дескрипторы, не относящиеся к данным (только __get__) уступают __dict__.
class DataDesc:
def __get__(self, obj, t): return "data"
def __set__(self, obj, val): pass
class NonDataDesc:
def __get__(self, obj, t): return "nondata"
class C:
d = DataDesc()
n = NonDataDesc()
c = C()
c.__dict__["n"] = "instance" # shadows the non-data desc
c.__dict__["d"] = "instance" # does NOT shadow data desc
print(c.n) # instance
print(c.d) # dataСигнатура __get__
__get__(self, obj, objtype): obj — это экземпляр (или None при обращении через класс), а objtype — класс.
class Verbose:
def __get__(self, obj, objtype=None):
if obj is None:
return self # accessed on class
return f"value for {obj!r}"
class Widget:
label = Verbose()
print(Widget.label) # <Verbose object>
print(Widget().label) # value for <Widget object>__set__ и проверка
Реализуйте __set__(self, obj, value), чтобы проверять или преобразовывать значение перед его сохранением.
class PositiveInt:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, val):
if not isinstance(val, int) or val <= 0:
raise ValueError(f"{self.name} must be a positive int")
obj.__dict__[self.name] = val
class Rect:
width = PositiveInt()
height = PositiveInt()
r = Rect()
r.width = 10
# r.width = -1 # ValueError__set_name__
В Python 3.6 и более поздних версиях при назначении дескриптора классу Python вызывает у него __set_name__(owner, name), предоставляя доступ к имени собственного атрибута.
class Typed:
def __set_name__(self, owner, name):
self.public = name
self.private = "_" + name
def __get__(self, obj, t):
return None if obj is None else getattr(obj, self.private, None)
def __set__(self, obj, val):
setattr(obj, self.private, val)
class User:
name = Typed()
u = User()
u.name = "Alice"
print(u.name) # Alice__delete__ для удаления атрибутов
Определите __delete__(self, obj), чтобы перехватывать del obj.attr.
class Protected:
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, v): obj.__dict__[self.name] = v
def __delete__(self, obj):
raise AttributeError(f"Cannot delete {self.name}")
class Config:
host = Protected()
c = Config(); c.host = "localhost"
# del c.host # AttributeErrorФункции — дескрипторы, не относящиеся к данным
Функции реализуют __get__, возвращая связанный метод при обращении через экземпляр. Так работает связывание методов в Python.
def greet(self):
return f"Hello from {self}"
class C: pass
C.greet = greet
c = C()
print(c.greet()) # Hello from <C object>classmethod и staticmethod — дескрипторы
classmethod и staticmethod — встроенные классы дескрипторов, которые оборачивают функции и изменяют возвращаемое значение __get__.
class MyClass:
@classmethod
def from_string(cls, s):
return cls()
# classmethod.__get__ returns a bound method with cls
# staticmethod.__get__ returns the plain functionДескриптор отложенного атрибута
Создайте дескриптор, который вычисляет значение один раз и кэширует его для каждого экземпляра.
class LazyAttr:
def __init__(self, func): self.func = func
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
if obj is None: return self
val = self.func(obj)
obj.__dict__[self.name] = val # shadows descriptor
return val
class Report:
@LazyAttr
def summary(self):
print("computing...")
return "done"
r = Report()
print(r.summary) # computing... done
print(r.summary) # done (cached)property — дескриптор
property сам является дескриптором данных, реализованным на C. Понимание дескрипторов помогает понять, как работают @property, @classmethod и @staticmethod.
class Circle:
def __init__(self, r): self._r = r
@property
def radius(self): return self._r
@radius.setter
def radius(self, v):
if v < 0: raise ValueError
self._r = v
# property is: property.__get__ = fget, property.__set__ = fsetПорядок поиска дескрипторов
Порядок поиска атрибутов в Python: 1) дескрипторы данных из типа (MRO), 2) __dict__ экземпляра, 3) дескрипторы без данных и переменные класса.
# Pseudocode for obj.attr:
# 1. Check type(obj).__mro__ for a data descriptor
# 2. Check obj.__dict__
# 3. Check type(obj).__mro__ for non-data descriptor or class var
# 4. Raise AttributeErrorБыстрая проверка
Какой тип дескриптора имеет приоритет над __dict__ экземпляра?
Итоги
Дескрипторы перехватывают доступ к атрибутам с помощью __get__, __set__, __delete__. Дескрипторы данных скрывают __dict__ экземпляра, а дескрипторы без данных — нет. Используйте __set_name__, чтобы получить имя атрибута. property, classmethod и staticmethod — все это дескрипторы.
Часто задаваемые вопросы
Урок «Дескрипторы: __get__, __set__, __delete__» бесплатный?
Да — полный текст урока «Дескрипторы: __get__, __set__, __delete__» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Python Academy, подпишись на CoddyKit PRO. Курс Python Academy содержит 4 уроков всего.
Чему я научусь в уроке «Дескрипторы: __get__, __set__, __delete__»?
Реализуйте протокол дескрипторов для управления доступом к атрибутам. Ты практикуешь Python Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Python Academy?
Предыдущий опыт не требуется. Python Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Дескрипторы: __get__, __set__, __delete__»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Python Academy?
Да. Каждый урок Python Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Как создаются классы Python
- Написание пользовательских метаклассов
- Дескрипторы: __get__, __set__, __delete__
- __slots__ и оптимизация памяти