0Pricing
Python Academy · Урок

Дескрипторы: __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 — локальная установка не требуется.

Все уроки этого курса

  1. Как создаются классы Python
  2. Написание пользовательских метаклассов
  3. Дескрипторы: __get__, __set__, __delete__
  4. __slots__ и оптимизация памяти
← Назад к Python Academy