Документация Python неофициальный перевод

descriptor.md

1107 строк · 67.5 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.9/howto/descriptor.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# [Руководство по дескрипторам](https://python-all.ru/3.9/howto/descriptor.html#id1)89**Автор**1011Raymond Hettinger1213**Контакт**1415\<python at rcn dot com\>1617Содержание1819- [Руководство по дескрипторам](https://python-all.ru/3.9/howto/descriptor.html#descriptor-howto-guide)2021  - [Введение](https://python-all.ru/3.9/howto/descriptor.html#primer)2223    - [Простой пример: дескриптор, возвращающий константу](https://python-all.ru/3.9/howto/descriptor.html#simple-example-a-descriptor-that-returns-a-constant)24    - [Динамический поиск](https://python-all.ru/3.9/howto/descriptor.html#dynamic-lookups)25    - [Управляемые атрибуты](https://python-all.ru/3.9/howto/descriptor.html#managed-attributes)26    - [Настраиваемые имена](https://python-all.ru/3.9/howto/descriptor.html#customized-names)27    - [Заключительные мысли](https://python-all.ru/3.9/howto/descriptor.html#closing-thoughts)28  - [Полный практический пример](https://python-all.ru/3.9/howto/descriptor.html#complete-practical-example)2930    - [Класс-валидатор](https://python-all.ru/3.9/howto/descriptor.html#validator-class)31    - [Пользовательские валидаторы](https://python-all.ru/3.9/howto/descriptor.html#custom-validators)32    - [Практическое применение](https://python-all.ru/3.9/howto/descriptor.html#practical-application)33  - [Техническое руководство](https://python-all.ru/3.9/howto/descriptor.html#technical-tutorial)3435    - [Аннотация](https://python-all.ru/3.9/howto/descriptor.html#abstract)36    - [Определение и введение](https://python-all.ru/3.9/howto/descriptor.html#definition-and-introduction)37    - [Протокол дескриптора](https://python-all.ru/3.9/howto/descriptor.html#descriptor-protocol)38    - [Обзор вызова дескриптора](https://python-all.ru/3.9/howto/descriptor.html#overview-of-descriptor-invocation)39    - [Вызов из экземпляра](https://python-all.ru/3.9/howto/descriptor.html#invocation-from-an-instance)40    - [Вызов из класса](https://python-all.ru/3.9/howto/descriptor.html#invocation-from-a-class)41    - [Вызов через super](https://python-all.ru/3.9/howto/descriptor.html#invocation-from-super)42    - [Сводка логики вызова](https://python-all.ru/3.9/howto/descriptor.html#summary-of-invocation-logic)43    - [Автоматическое уведомление об имени](https://python-all.ru/3.9/howto/descriptor.html#automatic-name-notification)44    - [Пример ORM](https://python-all.ru/3.9/howto/descriptor.html#orm-example)45  - [Эквиваленты на чистом Python](https://python-all.ru/3.9/howto/descriptor.html#pure-python-equivalents)4647    - [Свойства](https://python-all.ru/3.9/howto/descriptor.html#properties)48    - [Функции и методы](https://python-all.ru/3.9/howto/descriptor.html#functions-and-methods)49    - [Виды методов](https://python-all.ru/3.9/howto/descriptor.html#kinds-of-methods)50    - [Статические методы](https://python-all.ru/3.9/howto/descriptor.html#static-methods)51    - [Методы класса](https://python-all.ru/3.9/howto/descriptor.html#class-methods)52    - [Объекты-члены и \_\_slots\_\_](https://python-all.ru/3.9/howto/descriptor.html#member-objects-and-slots)5354[Дескрипторы](https://python-all.ru/3.9/glossary.html#term-descriptor) позволяют объектам настраивать поиск, хранение и удаление атрибутов.5556В этом руководстве четыре основных раздела:57581. «Введение» даёт базовый обзор, плавно переходя от простых примеров, добавляя по одной возможности за раз. Начните отсюда, если вы новичок в дескрипторах.592. Второй раздел показывает полный практический пример дескриптора. Если вы уже знаете основы, начинайте с него.603. Третий раздел представляет более техническое руководство, которое углубляется в детальные механизмы работы дескрипторов. Большинству людей не нужна такая степень детализации.614. Последний раздел содержит чистые аналоги Python для встроенных дескрипторов, написанных на C. Прочитайте его, если вам интересно, как функции превращаются в связанные методы или как реализованы распространённые инструменты, такие как [`classmethod()`](https://python-all.ru/3.9/library/functions.html#classmethod), [`staticmethod()`](https://python-all.ru/3.9/library/functions.html#staticmethod), [`property()`](https://python-all.ru/3.9/library/functions.html#property) и [\_\_slots\_\_](https://python-all.ru/3.9/glossary.html#term-slots).6263## [Введение](https://python-all.ru/3.9/howto/descriptor.html#id2)6465В этом введении мы начнём с самого простого возможного примера, а затем будем добавлять новые возможности одну за другой.6667### [Простой пример: дескриптор, возвращающий константу](https://python-all.ru/3.9/howto/descriptor.html#id3)6869Класс `Ten` – это дескриптор, который всегда возвращает константу `10` из своего метода [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__):7071```python72class Ten:73    def __get__(self, obj, objtype=None):74        return 1075```7677Чтобы использовать дескриптор, его нужно сохранить как переменную класса в другом классе:7879```python80class A:81    x = 5                       # Обычный атрибут класса82    y = Ten()                   # Экземпляр дескриптора83```8485Интерактивный сеанс показывает разницу между обычным поиском атрибута и поиском через дескриптор:8687```pycon88>>> a = A()                     # Создать экземпляр класса A89>>> a.x                         # Обычный поиск атрибута90591>>> a.y                         # Поиск через дескриптор921093```9495При поиске атрибута `a.x` оператор точки находит ключ `x` и значение `5` в словаре класса. При поиске `a.y` оператор точки находит экземпляр дескриптора, распознаваемый по его методу `__get__`, и вызывает этот метод, который возвращает `10`.9697Обратите внимание, что значение `10` не хранится ни в словаре класса, ни в словаре экземпляра. Вместо этого значение `10` вычисляется по требованию.9899Этот пример показывает, как работает простой дескриптор, но он не очень полезен. Для получения констант лучше подойдёт обычный поиск атрибута.100101В следующем разделе мы создадим нечто более полезное – динамический поиск.102103### [Динамический поиск](https://python-all.ru/3.9/howto/descriptor.html#id4)104105Интересные дескрипторы обычно выполняют вычисления вместо возврата констант:106107```python108import os109110class DirectorySize:111112    def __get__(self, obj, objtype=None):113        return len(os.listdir(obj.dirname))114115class Directory:116117    size = DirectorySize()              # Экземпляр дескриптора118119    def __init__(self, dirname):120        self.dirname = dirname          # Обычный атрибут экземпляра121```122123Интерактивный сеанс показывает, что поиск динамический – каждый раз вычисляются разные, обновлённые ответы:124125```python126>>> s = Directory('songs')127>>> g = Directory('games')128>>> s.size                              # В каталоге songs двадцать файлов12920130>>> g.size                              # В каталоге games три файла1313132>>> os.remove('games/chess')            # Удалить игру133>>> g.size                              # Счётчик файлов обновляется автоматически1342135```136137Помимо демонстрации того, как дескрипторы могут выполнять вычисления, этот пример также раскрывает назначение параметров [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__). Параметр *self* – это *size*, экземпляр *DirectorySize*. Параметр *obj* – это либо *g*, либо *s*, экземпляр *Directory*. Именно параметр *obj* позволяет методу [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__) узнать целевой каталог. Параметр *objtype* – это класс *Directory*.138139### [Управляемые атрибуты](https://python-all.ru/3.9/howto/descriptor.html#id5)140141Популярное применение дескрипторов – управление доступом к данным экземпляра. Дескриптор назначается публичному атрибуту в словаре класса, в то время как фактические данные хранятся как приватный атрибут в словаре экземпляра. Методы дескриптора [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__) и [`__set__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set__) вызываются при обращении к публичному атрибуту.142143В следующем примере *age* – публичный атрибут, а *\_age* – приватный атрибут. При обращении к публичному атрибуту дескриптор регистрирует поиск или обновление:144145```python146import logging147148logging.basicConfig(level=logging.INFO)149150class LoggedAgeAccess:151152    def __get__(self, obj, objtype=None):153        value = obj._age154        logging.info('Accessing %r giving %r', 'age', value)155        return value156157    def __set__(self, obj, value):158        logging.info('Updating %r to %r', 'age', value)159        obj._age = value160161class Person:162163    age = LoggedAgeAccess()             # Экземпляр дескриптора164165    def __init__(self, name, age):166        self.name = name                # Обычный атрибут экземпляра167        self.age = age                  # Вызывает __set__()168169    def birthday(self):170        self.age += 1                   # Вызывает и __get__(), и __set__()171```172173Интерактивный сеанс показывает, что весь доступ к управляемому атрибуту *age* регистрируется, а обычный атрибут *name* – нет:174175```pycon176>>> mary = Person('Mary M', 30)         # Первоначальное обновление возраста записывается177INFO:root:Updating 'age' to 30178>>> dave = Person('David D', 40)179INFO:root:Updating 'age' to 40180181>>> vars(mary)                          # Фактические данные находятся в приватном атрибуте182{'name': 'Mary M', '_age': 30}183>>> vars(dave)184{'name': 'David D', '_age': 40}185186>>> mary.age                            # Получить доступ к данным и записать поиск187INFO:root:Accessing 'age' giving 3018830189>>> mary.birthday()                     # Обновления также записываются190INFO:root:Accessing 'age' giving 30191INFO:root:Updating 'age' to 31192193>>> dave.name                           # Обычный поиск атрибута не записывается194'David D'195>>> dave.age                            # Записывается только управляемый атрибут196INFO:root:Accessing 'age' giving 4019740198```199200Одна из главных проблем этого примера – приватное имя *\_age* жёстко задано в классе *LoggedAgeAccess*. Это означает, что каждый экземпляр может иметь только один регистрируемый атрибут, и его имя нельзя изменить. В следующем примере мы исправим эту проблему.201202### [Настраиваемые имена](https://python-all.ru/3.9/howto/descriptor.html#id6)203204Когда класс использует дескрипторы, он может сообщить каждому дескриптору, какое имя переменной было использовано.205206В этом примере класс `Person` имеет два экземпляра дескриптора: *name* и *age*. Когда класс `Person` определён, он делает обратный вызов [`__set_name__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set_name__) в *LoggedAccess*, чтобы имена полей были записаны, давая каждому дескриптору свои *public\_name* и *private\_name*:207208```python209import logging210211logging.basicConfig(level=logging.INFO)212213class LoggedAccess:214215    def __set_name__(self, owner, name):216        self.public_name = name217        self.private_name = '_' + name218219    def __get__(self, obj, objtype=None):220        value = getattr(obj, self.private_name)221        logging.info('Accessing %r giving %r', self.public_name, value)222        return value223224    def __set__(self, obj, value):225        logging.info('Updating %r to %r', self.public_name, value)226        setattr(obj, self.private_name, value)227228class Person:229230    name = LoggedAccess()                # Первый экземпляр дескриптора231    age = LoggedAccess()                 # Второй экземпляр дескриптора232233    def __init__(self, name, age):234        self.name = name                 # Вызывает первый дескриптор235        self.age = age                   # Вызывает второй дескриптор236237    def birthday(self):238        self.age += 1239```240241Интерактивный сеанс показывает, что класс `Person` вызвал [`__set_name__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set_name__), чтобы имена полей были записаны. Здесь мы вызываем [`vars()`](https://python-all.ru/3.9/library/functions.html#vars) для поиска дескриптора без его активации:242243```pycon244>>> vars(vars(Person)['name'])245{'public_name': 'name', 'private_name': '_name'}246>>> vars(vars(Person)['age'])247{'public_name': 'age', 'private_name': '_age'}248```249250Новый класс теперь регистрирует доступ как к *name*, так и к *age*:251252```pycon253>>> pete = Person('Peter P', 10)254INFO:root:Updating 'name' to 'Peter P'255INFO:root:Updating 'age' to 10256>>> kate = Person('Catherine C', 20)257INFO:root:Updating 'name' to 'Catherine C'258INFO:root:Updating 'age' to 20259```260261Два экземпляра *Person* содержат только приватные имена:262263```pycon264>>> vars(pete)265{'_name': 'Peter P', '_age': 10}266>>> vars(kate)267{'_name': 'Catherine C', '_age': 20}268```269270### [Заключительные мысли](https://python-all.ru/3.9/howto/descriptor.html#id7)271272[Дескриптором](https://python-all.ru/3.9/glossary.html#term-descriptor) мы называем любой объект, который определяет [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__), [`__set__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set__) или [`__delete__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__delete__).273274Дескрипторы могут дополнительно иметь метод [`__set_name__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set_name__). Он используется только в случаях, когда дескриптору нужно знать либо класс, в котором он был создан, либо имя переменной класса, которой он был присвоен. (Если этот метод присутствует, он вызывается, даже если класс не является дескриптором.)275276Дескрипторы вызываются оператором «точка» при поиске атрибута. Если дескриптор получен косвенно через `vars(some_class)[descriptor_name]`, возвращается экземпляр дескриптора без его вызова.277278Дескрипторы работают только при использовании в качестве переменных класса. Если их поместить в экземпляры, они не оказывают никакого эффекта.279280Основная мотивация дескрипторов – предоставить точку входа, позволяющую объектам, хранящимся в переменных класса, контролировать то, что происходит во время поиска атрибута.281282Традиционно вызывающий класс контролирует то, что происходит во время поиска. Дескрипторы инвертируют это отношение и позволяют данным, к которым осуществляется поиск, влиять на процесс.283284Дескрипторы используются повсеместно в языке. Именно так функции превращаются в связанные методы. Распространенные инструменты, такие как [`classmethod()`](https://python-all.ru/3.9/library/functions.html#classmethod), [`staticmethod()`](https://python-all.ru/3.9/library/functions.html#staticmethod), [`property()`](https://python-all.ru/3.9/library/functions.html#property) и [`functools.cached_property()`](https://python-all.ru/3.9/library/functools.html#functools.cached_property), все реализованы как дескрипторы.285286## [Полный практический пример](https://python-all.ru/3.9/howto/descriptor.html#id8)287288В этом примере создается практичный и мощный инструмент для поиска пресловутых труднообнаруживаемых ошибок повреждения данных.289290### [Класс Validator](https://python-all.ru/3.9/howto/descriptor.html#id9)291292Валидатор – это дескриптор для управляемого доступа к атрибутам. Перед сохранением любых данных он проверяет, соответствует ли новое значение различным ограничениям типа и диапазона. Если эти ограничения не соблюдены, вызывается исключение, чтобы предотвратить повреждение данных на раннем этапе.293294Этот класс `Validator` является одновременно [абстрактным базовым классом](https://python-all.ru/3.9/glossary.html#term-abstract-base-class) и дескриптором управляемого атрибута:295296```python297from abc import ABC, abstractmethod298299class Validator(ABC):300301    def __set_name__(self, owner, name):302        self.private_name = '_' + name303304    def __get__(self, obj, objtype=None):305        return getattr(obj, self.private_name)306307    def __set__(self, obj, value):308        self.validate(value)309        setattr(obj, self.private_name, value)310311    @abstractmethod312    def validate(self, value):313        pass314```315316Пользовательские валидаторы должны наследовать от `Validator` и предоставлять метод `validate()` для проверки различных ограничений по мере необходимости.317318### [Пользовательские валидаторы](https://python-all.ru/3.9/howto/descriptor.html#id10)319320Вот три практические утилиты для проверки данных:3213221. `OneOf` проверяет, что значение является одним из ограниченного набора опций.3232. `Number` проверяет, что значение является либо [`int`](https://python-all.ru/3.9/library/functions.html#int), либо [`float`](https://python-all.ru/3.9/library/functions.html#float). Опционально он проверяет, что значение находится между заданным минимумом или максимумом.3243. `String` проверяет, что значение является [`str`](https://python-all.ru/3.9/library/stdtypes.html#str). Опционально он проверяет заданную минимальную или максимальную длину. Он также может проверять определенный пользователем [предикат](https://python-all.ru/3.9/howto/descriptor.html).325326```python327class OneOf(Validator):328329    def __init__(self, *options):330        self.options = set(options)331332    def validate(self, value):333        if value not in self.options:334            raise ValueError(f'Expected {value!r} to be one of {self.options!r}')335336class Number(Validator):337338    def __init__(self, minvalue=None, maxvalue=None):339        self.minvalue = minvalue340        self.maxvalue = maxvalue341342    def validate(self, value):343        if not isinstance(value, (int, float)):344            raise TypeError(f'Expected {value!r} to be an int or float')345        if self.minvalue is not None and value < self.minvalue:346            raise ValueError(347                f'Expected {value!r} to be at least {self.minvalue!r}'348            )349        if self.maxvalue is not None and value > self.maxvalue:350            raise ValueError(351                f'Expected {value!r} to be no more than {self.maxvalue!r}'352            )353354class String(Validator):355356    def __init__(self, minsize=None, maxsize=None, predicate=None):357        self.minsize = minsize358        self.maxsize = maxsize359        self.predicate = predicate360361    def validate(self, value):362        if not isinstance(value, str):363            raise TypeError(f'Expected {value!r} to be an str')364        if self.minsize is not None and len(value) < self.minsize:365            raise ValueError(366                f'Expected {value!r} to be no smaller than {self.minsize!r}'367            )368        if self.maxsize is not None and len(value) > self.maxsize:369            raise ValueError(370                f'Expected {value!r} to be no bigger than {self.maxsize!r}'371            )372        if self.predicate is not None and not self.predicate(value):373            raise ValueError(374                f'Expected {self.predicate} to be true for {value!r}'375            )376```377378### [Практическое применение](https://python-all.ru/3.9/howto/descriptor.html#id11)379380Вот как валидаторы данных могут быть использованы в реальном классе:381382```python383class Component:384385    name = String(minsize=3, maxsize=10, predicate=str.isupper)386    kind = OneOf('wood', 'metal', 'plastic')387    quantity = Number(minvalue=0)388389    def __init__(self, name, kind, quantity):390        self.name = name391        self.kind = kind392        self.quantity = quantity393```394395Дескрипторы предотвращают создание недопустимых экземпляров:396397```pycon398>>> Component('Widget', 'metal', 5)      # Заблокировано: 'Widget' не в верхнем регистре399Traceback (most recent call last):400    ...401ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget'402403>>> Component('WIDGET', 'metle', 5)      # Заблокировано: 'metle' написано с ошибкой404Traceback (most recent call last):405    ...406ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'}407408>>> Component('WIDGET', 'metal', -5)     # Заблокировано: -5 отрицательное409Traceback (most recent call last):410    ...411ValueError: Expected -5 to be at least 0412>>> Component('WIDGET', 'metal', 'V')    # Заблокировано: 'V' не является числом413Traceback (most recent call last):414    ...415TypeError: Expected 'V' to be an int or float416417>>> c = Component('WIDGET', 'metal', 5)  # Разрешено: входные данные корректны418```419420## [Техническое руководство](https://python-all.ru/3.9/howto/descriptor.html#id12)421422Далее следует более техническое руководство по механике и деталям того, как работают дескрипторы.423424### [Аннотация](https://python-all.ru/3.9/howto/descriptor.html#id13)425426Определяет дескрипторы, обобщает протокол и показывает, как дескрипторы вызываются. Приводится пример, показывающий, как работают объектно-реляционные отображения.427428Изучение дескрипторов не только открывает доступ к более широкому набору инструментов, но и дает более глубокое понимание того, как работает Python.429430### [Определение и введение](https://python-all.ru/3.9/howto/descriptor.html#id14)431432В общем случае дескриптор – это значение атрибута, которое имеет один из методов протокола дескриптора. Эти методы: [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__), [`__set__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set__), и [`__delete__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__delete__). Если для атрибута определен хотя бы один из этих методов, то такой атрибут называется [дескриптором](https://python-all.ru/3.9/glossary.html#term-descriptor).433434Поведение по умолчанию при доступе к атрибуту – получить, установить или удалить атрибут из словаря объекта. Например, `a.x` имеет цепочку поиска, начиная с `a.__dict__['x']`, затем `type(a).__dict__['x']` и продолжая порядком разрешения методов `type(a)`. Если найденное значение является объектом, определяющим один из методов дескриптора, то Python может переопределить поведение по умолчанию и вместо этого вызвать метод дескриптора. Где это происходит в цепочке приоритетов, зависит от того, какие методы дескриптора были определены.435436Дескрипторы – это мощный протокол общего назначения. Они являются механизмом, лежащим в основе свойств, методов, статических методов, методов класса и [`super()`](https://python-all.ru/3.9/library/functions.html#super). Они используются во всем Python. Дескрипторы упрощают базовый код на C и предоставляют гибкий набор новых инструментов для повседневных программ на Python.437438### [Протокол дескриптора](https://python-all.ru/3.9/howto/descriptor.html#id15)439440`descr.__get__(self, obj, type=None) -> value`441442`descr.__set__(self, obj, value) -> None`443444`descr.__delete__(self, obj) -> None`445446Вот и всё. Определите любой из этих методов – и объект считается дескриптором и может переопределять поведение по умолчанию при поиске в качестве атрибута.447448Если объект определяет [`__set__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set__) или [`__delete__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__delete__), он считается дескриптором данных. Дескрипторы, которые определяют только [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__), называются дескрипторами, не являющимися дескрипторами данных (они часто используются для методов, но возможно и другое применение).449450Дескрипторы данных и не-данных различаются тем, как вычисляются переопределения по отношению к записям в словаре экземпляра. Если в словаре экземпляра есть запись с тем же именем, что и у дескриптора данных, дескриптор данных имеет приоритет. Если в словаре экземпляра есть запись с тем же именем, что и у дескриптора не-данных, приоритет имеет запись словаря.451452Чтобы создать дескриптор данных только для чтения, определите и [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__), и [`__set__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set__), причём [`__set__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set__) должен возбуждать [`AttributeError`](https://python-all.ru/3.9/library/exceptions.html#AttributeError) при вызове. Определения метода [`__set__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set__) с заглушкой, возбуждающей исключение, достаточно, чтобы сделать его дескриптором данных.453454### [Обзор вызова дескрипторов](https://python-all.ru/3.9/howto/descriptor.html#id16)455456Дескриптор можно вызвать напрямую с помощью `desc.__get__(obj)` или `desc.__get__(None, cls)`.457458Но чаще дескриптор вызывается автоматически при доступе к атрибуту.459460Выражение `obj.x` ищет атрибут `x` в цепочке пространств имён для `obj`. Если поиск находит дескриптор вне экземпляра `__dict__`, его метод [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__) вызывается в соответствии с правилами приоритета, перечисленными ниже.461462Детали вызова зависят от того, является ли `obj` объектом, классом или экземпляром super.463464### [Вызов из экземпляра](https://python-all.ru/3.9/howto/descriptor.html#id17)465466Поиск в экземпляре просматривает цепочку пространств имён, отдавая наивысший приоритет дескрипторам данных, затем переменным экземпляра, затем дескрипторам не-данных, затем переменным класса и, наконец, [`__getattr__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattr__), если он предусмотрен.467468Если дескриптор найден для `a.x`, он вызывается с: `desc.__get__(a, type(a))`.469470Логика точечного поиска находится в [`object.__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__). Вот эквивалент на чистом Python:471472```python473def object_getattribute(obj, name):474    "Emulate PyObject_GenericGetAttr() in Objects/object.c"475    null = object()476    objtype = type(obj)477    cls_var = getattr(objtype, name, null)478    descr_get = getattr(type(cls_var), '__get__', null)479    if descr_get is not null:480        if (hasattr(type(cls_var), '__set__')481            or hasattr(type(cls_var), '__delete__')):482            return descr_get(cls_var, obj, objtype)     # дескриптор данных483    if hasattr(obj, '__dict__') and name in vars(obj):484        return vars(obj)[name]                          # переменная экземпляра485    if descr_get is not null:486        return descr_get(cls_var, obj, objtype)         # дескриптор без данных487    if cls_var is not null:488        return cls_var                                  # переменная класса489    raise AttributeError(name)490```491492Обратите внимание: в коде [`__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__) нет ловушки [`__getattr__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattr__). Именно поэтому прямой вызов [`__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__) или вызов с `super().__getattribute__` полностью обходит [`__getattr__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattr__).493494Вместо этого именно оператор точки и функция [`getattr()`](https://python-all.ru/3.9/library/functions.html#getattr) отвечают за вызов [`__getattr__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattr__) всякий раз, когда [`__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__) возбуждает [`AttributeError`](https://python-all.ru/3.9/library/exceptions.html#AttributeError). Их логика инкапсулирована во вспомогательной функции:495496```python497def getattr_hook(obj, name):498    "Emulate slot_tp_getattr_hook() in Objects/typeobject.c"499    try:500        return obj.__getattribute__(name)501    except AttributeError:502        if not hasattr(type(obj), '__getattr__'):503            raise504    return type(obj).__getattr__(obj, name)             # getattr505```506507### [Вызов из класса](https://python-all.ru/3.9/howto/descriptor.html#id18)508509Логика точечного поиска, такого как `A.x`, находится в `type.__getattribute__()`. Шаги аналогичны шагам для [`object.__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__), но поиск по словарю экземпляра заменяется поиском по [порядку разрешения методов](https://python-all.ru/3.9/glossary.html#term-method-resolution-order) класса.510511Если дескриптор найден, он вызывается с `desc.__get__(None, A)`.512513Полную реализацию на C можно найти в `type_getattro()` и `_PyType_Lookup()` в [Objects/typeobject.c](https://python-all.ru/src/3.9/Objects/typeobject.c).514515### [Вызов из super](https://python-all.ru/3.9/howto/descriptor.html#id19)516517Логика точечного поиска в super находится в методе [`__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__) объекта, возвращаемого [`super()`](https://python-all.ru/3.9/library/functions.html#super).518519Точечный поиск, такой как `super(A, obj).m`, ищет в `obj.__class__.__mro__` базовый класс `B`, следующий сразу за `A`, и затем возвращает `B.__dict__['m'].__get__(obj, A)`. Если это не дескриптор, `m` возвращается без изменений.520521Полную реализацию на C можно найти в `super_getattro()` в [Objects/typeobject.c](https://python-all.ru/src/3.9/Objects/typeobject.c). Эквивалент на чистом Python можно найти в [руководстве Гвидо](https://python-all.ru/3.9/howto/descriptor.html).522523### [Сводка по логике вызова](https://python-all.ru/3.9/howto/descriptor.html#id20)524525Механизм дескрипторов встроен в методы [`__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__) для [`object`](https://python-all.ru/3.9/library/functions.html#object), [`type`](https://python-all.ru/3.9/library/functions.html#type) и [`super()`](https://python-all.ru/3.9/library/functions.html#super).526527Важно запомнить следующее:528529- Дескрипторы вызываются методом [`__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__).530- Классы наследуют этот механизм от [`object`](https://python-all.ru/3.9/library/functions.html#object), [`type`](https://python-all.ru/3.9/library/functions.html#type) или [`super()`](https://python-all.ru/3.9/library/functions.html#super).531- Переопределение [`__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__) предотвращает автоматические вызовы дескрипторов, поскольку вся логика дескрипторов находится в этом методе.532- [`object.__getattribute__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__getattribute__) и `type.__getattribute__()` по-разному вызывают [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__). Первый включает экземпляр и может включать класс. Второй передаёт `None` в качестве экземпляра и всегда включает класс.533- Дескрипторы данных всегда переопределяют словари экземпляров.534- Дескрипторы не-данных могут быть переопределены словарями экземпляров.535536### [Автоматическое уведомление об имени](https://python-all.ru/3.9/howto/descriptor.html#id21)537538Иногда желательно, чтобы дескриптор знал имя переменной класса, которой он был присвоен. Когда создаётся новый класс, метакласс [`type`](https://python-all.ru/3.9/library/functions.html#type) сканирует словарь нового класса. Если какие-либо записи являются дескрипторами и в них определён [`__set_name__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set_name__), этот метод вызывается с двумя аргументами. *Владелец* – это класс, в котором используется дескриптор, а *имя* – это переменная класса, которой был присвоен дескриптор.539540Детали реализации находятся в `type_new()` и `set_names()` в [Objects/typeobject.c](https://python-all.ru/src/3.9/Objects/typeobject.c).541542Поскольку логика обновления находится в `type.__new__()`, уведомления происходят только в момент создания класса. Если дескрипторы добавляются в класс после этого, [`__set_name__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__set_name__) потребуется вызвать вручную.543544### [Пример ORM](https://python-all.ru/3.9/howto/descriptor.html#id22)545546Следующий код представляет собой упрощённый скелет, показывающий, как дескрипторы данных могут использоваться для реализации [объектно-реляционного отображения](https://python-all.ru/3.9/howto/descriptor.html).547548Основная идея в том, что данные хранятся во внешней базе данных. Экземпляры Python хранят только ключи к таблицам базы. Дескрипторы берут на себя поиск или обновление:549550```python551class Field:552553    def __set_name__(self, owner, name):554        self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.key}=?;'555        self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.key}=?;'556557    def __get__(self, obj, objtype=None):558        return conn.execute(self.fetch, [obj.key]).fetchone()[0]559560    def __set__(self, obj, value):561        conn.execute(self.store, [value, obj.key])562        conn.commit()563```564565Можно использовать класс `Field` для определения [моделей](https://python-all.ru/3.9/howto/descriptor.html), описывающих схему для каждой таблицы в базе данных:566567```python568class Movie:569    table = 'Movies'                    # Имя таблицы570    key = 'title'                       # Первичный ключ571    director = Field()572    year = Field()573574    def __init__(self, key):575        self.key = key576577class Song:578    table = 'Music'579    key = 'title'580    artist = Field()581    year = Field()582    genre = Field()583584    def __init__(self, key):585        self.key = key586```587588Чтобы использовать модели, сначала нужно подключиться к базе данных:589590```python591>>> import sqlite3592>>> conn = sqlite3.connect('entertainment.db')593```594595Интерактивный сеанс показывает, как данные извлекаются из базы данных и как их можно обновить:596597```pycon598>>> Movie('Star Wars').director599'George Lucas'600>>> jaws = Movie('Jaws')601>>> f'Released in {jaws.year} by {jaws.director}'602'Released in 1975 by Steven Spielberg'603604>>> Song('Country Roads').artist605'John Denver'606607>>> Movie('Star Wars').director = 'J.J. Abrams'608>>> Movie('Star Wars').director609'J.J. Abrams'610```611612## [Эквиваленты на чистом Python](https://python-all.ru/3.9/howto/descriptor.html#id23)613614Протокол дескрипторов прост и открывает захватывающие возможности. Несколько вариантов использования настолько распространены, что они уже встроены в стандартные инструменты. Свойства (properties), связанные методы, статические методы, методы класса и \_\_slots\_\_ – все они основаны на протоколе дескрипторов.615616### [Свойства](https://python-all.ru/3.9/howto/descriptor.html#id24)617618Вызов [`property()`](https://python-all.ru/3.9/library/functions.html#property) – это краткий способ создания дескриптора данных, который вызывает функцию при доступе к атрибуту. Его сигнатура:619620```python621property(fget=None, fset=None, fdel=None, doc=None) -> property622```623624В документации показан типичный пример использования для определения управляемого атрибута `x`:625626```python627class C:628    def getx(self): return self.__x629    def setx(self, value): self.__x = value630    def delx(self): del self.__x631    x = property(getx, setx, delx, "I'm the 'x' property.")632```633634Чтобы увидеть, как [`property()`](https://python-all.ru/3.9/library/functions.html#property) реализуется через протокол дескрипторов, вот эквивалент на чистом Python:635636```python637class Property:638    "Emulate PyProperty_Type() in Objects/descrobject.c"639640    def __init__(self, fget=None, fset=None, fdel=None, doc=None):641        self.fget = fget642        self.fset = fset643        self.fdel = fdel644        if doc is None and fget is not None:645            doc = fget.__doc__646        self.__doc__ = doc647648    def __get__(self, obj, objtype=None):649        if obj is None:650            return self651        if self.fget is None:652            raise AttributeError("unreadable attribute")653        return self.fget(obj)654655    def __set__(self, obj, value):656        if self.fset is None:657            raise AttributeError("can't set attribute")658        self.fset(obj, value)659660    def __delete__(self, obj):661        if self.fdel is None:662            raise AttributeError("can't delete attribute")663        self.fdel(obj)664665    def getter(self, fget):666        return type(self)(fget, self.fset, self.fdel, self.__doc__)667668    def setter(self, fset):669        return type(self)(self.fget, fset, self.fdel, self.__doc__)670671    def deleter(self, fdel):672        return type(self)(self.fget, self.fset, fdel, self.__doc__)673```674675Встроенная функция [`property()`](https://python-all.ru/3.9/library/functions.html#property) помогает, когда пользовательский интерфейс предоставил доступ к атрибуту, а последующие изменения требуют вмешательства метода.676677Например, класс электронной таблицы может предоставлять доступ к значению ячейки через `Cell('b10').value`. Последующие улучшения программы требуют пересчёта ячейки при каждом доступе; однако программист не хочет влиять на существующий клиентский код, обращающийся к атрибуту напрямую. Решение – обернуть доступ к атрибуту значения в дескриптор данных свойства:678679```python680class Cell:681    ...682683    @property684    def value(self):685        "Recalculate the cell before returning value"686        self.recalc()687        return self._value688```689690В этом примере подойдёт как встроенная [`property()`](https://python-all.ru/3.9/library/functions.html#property), так и наш эквивалент `Property()`.691692### [Функции и методы](https://python-all.ru/3.9/howto/descriptor.html#id25)693694Объектно-ориентированные возможности Python построены на функциональной основе. С помощью дескрипторов, не являющихся дескрипторами данных, эти две парадигмы объединяются бесшовно.695696Функции, хранящиеся в словарях классов, превращаются в методы при вызове. Методы отличаются от обычных функций только тем, что экземпляр объекта добавляется перед остальными аргументами. По соглашению экземпляр называется *self*, но может называться *this* или любым другим именем переменной.697698Методы можно создавать вручную с помощью [`types.MethodType`](https://python-all.ru/3.9/library/types.html#types.MethodType), что примерно эквивалентно:699700```python701class MethodType:702    "Emulate PyMethod_Type in Objects/classobject.c"703704    def __init__(self, func, obj):705        self.__func__ = func706        self.__self__ = obj707708    def __call__(self, *args, **kwargs):709        func = self.__func__710        obj = self.__self__711        return func(obj, *args, **kwargs)712```713714Для поддержки автоматического создания методов функции включают метод [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__) для привязки методов при доступе к атрибуту. Это означает, что функции являются дескрипторами, не являющимися дескрипторами данных, которые возвращают связанные методы при точечном поиске от экземпляра. Вот как это работает:715716```python717class Function:718    ...719720    def __get__(self, obj, objtype=None):721        "Simulate func_descr_get() in Objects/funcobject.c"722        if obj is None:723            return self724        return MethodType(self, obj)725```726727Запуск следующего класса в интерпретаторе показывает, как дескриптор функции работает на практике:728729```python730class D:731    def f(self, x):732         return x733```734735Функция имеет атрибут [полное имя](https://python-all.ru/3.9/glossary.html#term-qualified-name) для поддержки интроспекции:736737```pycon738>>> D.f.__qualname__739'D.f'740```741742Доступ к функции через словарь класса не вызывает [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__). Вместо этого просто возвращается исходный объект функции:743744```python745>>> D.__dict__['f']746<function D.f at 0x00C45070>747```748749Точечный доступ от класса вызывает [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__), который просто возвращает исходную функцию без изменений:750751```python752>>> D.f753<function D.f at 0x00C45070>754```755756Интересное поведение происходит при точечном доступе от экземпляра. Точечный поиск вызывает [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__), который возвращает объект связанного метода:757758```python759>>> d = D()760>>> d.f761<bound method D.f of <__main__.D object at 0x00B18C90>>762```763764Внутренне связанный метод хранит исходную функцию и связанный экземпляр:765766```python767>>> d.f.__func__768<function D.f at 0x00C45070>769770>>> d.f.__self__771<__main__.D object at 0x1012e1f98>772```773774Если вы когда-нибудь задавались вопросом, откуда берётся *self* в обычных методах или откуда *cls* в методах класса – вот ответ!775776### [Виды методов](https://python-all.ru/3.9/howto/descriptor.html#id26)777778Дескрипторы, не являющиеся дескрипторами данных, предоставляют простой механизм для вариаций обычных шаблонов привязки функций к методам.779780Повторим: функции имеют метод [`__get__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__get__), чтобы их можно было преобразовать в метод при доступе как атрибуты. Дескриптор, не являющийся дескриптором данных, преобразует вызов `obj.f(*args)` в `f(obj, *args)`. Вызов `cls.f(*args)` становится `f(*args)`.781782Эта таблица обобщает привязку и два наиболее полезных её варианта:783784> | Преобразование | Вызывается от объекта | Вызывается от класса |785> | --- | --- | --- |786> | функция | f(obj, \*args) | f(\*args) |787> | staticmethod | f(\*args) | f(\*args) |788> | classmethod | f(type(obj), \*args) | f(cls, \*args) |789790### [Статические методы](https://python-all.ru/3.9/howto/descriptor.html#id27)791792Статические методы возвращают базовую функцию без изменений. Вызов `c.f` или `C.f` эквивалентен прямому поиску в `object.__getattribute__(c, "f")` или `object.__getattribute__(C, "f")`. В результате функция становится одинаково доступной как из объекта, так и из класса.793794Хорошими кандидатами для статических методов являются те, которые не ссылаются на переменную `self`.795796Например, в пакете статистики может быть класс-контейнер для экспериментальных данных. Класс предоставляет обычные методы для вычисления среднего, медианы и других описательных статистик, которые зависят от данных. Однако могут быть полезные функции, которые концептуально связаны, но не зависят от данных. Например, `erf(x)` – это удобная процедура преобразования, которая встречается в статистической работе, но не зависит напрямую от конкретного набора данных. Её можно вызвать как от объекта, так и от класса: `s.erf(1.5) --> .9332` или `Sample.erf(1.5) --> .9332`.797798Поскольку статические методы возвращают базовую функцию без изменений, примеры вызовов ничем не примечательны:799800```python801class E:802    @staticmethod803    def f(x):804        return x * 10805```806807```pycon808>>> E.f(3)80930810>>> E().f(3)81130812```813814Используя протокол дескриптора без данных, чистая версия [`staticmethod()`](https://python-all.ru/3.9/library/functions.html#staticmethod) на Python выглядела бы так:815816```python817class StaticMethod:818    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"819820    def __init__(self, f):821        self.f = f822823    def __get__(self, obj, objtype=None):824        return self.f825```826827### [Методы класса](https://python-all.ru/3.9/howto/descriptor.html#id28)828829В отличие от статических методов, методы класса перед вызовом функции добавляют ссылку на класс в начало списка аргументов. Этот формат одинаков как для вызова от объекта, так и от класса:830831```python832class F:833    @classmethod834    def f(cls, x):835        return cls.__name__, x836```837838```pycon839>>> F.f(3)840('F', 3)841>>> F().f(3)842('F', 3)843```844845Такое поведение полезно, когда методу нужна только ссылка на класс и он не зависит от данных, хранящихся в конкретном экземпляре. Одно из применений методов класса – создание альтернативных конструкторов класса. Например, метод класса [`dict.fromkeys()`](https://python-all.ru/3.9/library/stdtypes.html#dict.fromkeys) создаёт новый словарь из списка ключей. Эквивалент на чистом Python выглядит так:846847```python848class Dict(dict):849    @classmethod850    def fromkeys(cls, iterable, value=None):851        "Emulate dict_fromkeys() in Objects/dictobject.c"852        d = cls()853        for key in iterable:854            d[key] = value855        return d856```857858Теперь новый словарь уникальных ключей можно создать так:859860```pycon861>>> d = Dict.fromkeys('abracadabra')862>>> type(d) is Dict863True864>>> d865{'a': None, 'b': None, 'r': None, 'c': None, 'd': None}866```867868Используя протокол дескриптора без данных, чистая версия [`classmethod()`](https://python-all.ru/3.9/library/functions.html#classmethod) на Python выглядела бы так:869870```python871class ClassMethod:872    "Emulate PyClassMethod_Type() in Objects/funcobject.c"873874    def __init__(self, f):875        self.f = f876877    def __get__(self, obj, cls=None):878        if cls is None:879            cls = type(obj)880        if hasattr(type(self.f), '__get__'):881            return self.f.__get__(cls)882        return MethodType(self.f, cls)883```884885Путь кода для `hasattr(type(self.f), '__get__')` был добавлен в Python 3.9 и позволяет [`classmethod()`](https://python-all.ru/3.9/library/functions.html#classmethod) поддерживать цепочечные декораторы. Например, classmethod и property можно соединить в цепочку:886887```python888class G:889    @classmethod890    @property891    def __doc__(cls):892        return f'A doc for {cls.__name__!r}'893```894895```pycon896>>> G.__doc__897"A doc for 'G'"898```899900### [Объекты-члены и \_\_slots\_\_](https://python-all.ru/3.9/howto/descriptor.html#id29)901902Когда класс определяет `__slots__`, он заменяет словари экземпляров массивом фиксированной длины для значений слотов. С точки зрения пользователя это имеет несколько последствий:9039041. Обеспечивает немедленное обнаружение ошибок из-за опечаток в именах атрибутов. Разрешены только имена атрибутов, указанные в `__slots__`:905906```python907class Vehicle:908    __slots__ = ('id_number', 'make', 'model')909```910911```pycon912>>> auto = Vehicle()913>>> auto.id_nubmer = 'VYE483814LQEX'914Traceback (most recent call last):915    ...916AttributeError: 'Vehicle' object has no attribute 'id_nubmer'917```9189192. Помогает создавать неизменяемые объекты, где дескрипторы управляют доступом к приватным атрибутам, хранящимся в `__slots__`:920921```python922class Immutable:923924    __slots__ = ('_dept', '_name')          # Замена словаря экземпляра925926    def __init__(self, dept, name):927        self._dept = dept                   # Сохранение в частный атрибут928        self._name = name                   # Сохранение в частный атрибут929930    @property                               # Дескриптор только для чтения931    def dept(self):932        return self._dept933934    @property935    def name(self):                         # Дескриптор только для чтения936        return self._name937```938939```pycon940>>> mark = Immutable('Botany', 'Mark Watney')941>>> mark.dept942'Botany'943>>> mark.dept = 'Space Pirate'944Traceback (most recent call last):945    ...946AttributeError: can't set attribute947>>> mark.location = 'Mars'948Traceback (most recent call last):949    ...950AttributeError: 'Immutable' object has no attribute 'location'951```9529533. Экономит память. В 64-битной сборке Linux экземпляр с двумя атрибутами занимает 48 байт с `__slots__` и 152 байта без него. Этот [шаблон «приспособленец»](https://python-all.ru/3.9/howto/descriptor.html), вероятно, имеет значение только при создании большого количества экземпляров.9549554. Блокирует инструменты, подобные [`functools.cached_property()`](https://python-all.ru/3.9/library/functools.html#functools.cached_property), которым для корректной работы требуется словарь экземпляра:956957```python958from functools import cached_property959960class CP:961    __slots__ = ()                          # Устраняет словарь экземпляра962963    @cached_property                        # Требуется словарь экземпляра964    def pi(self):965        return 4 * sum((-1.0)**n / (2.0*n + 1.0)966                       for n in reversed(range(100_000)))967```968969```pycon970>>> CP().pi971Traceback (most recent call last):972  ...973TypeError: No '__dict__' attribute on 'CP' instance to cache 'pi' property.974```975976Невозможно создать точную замену `__slots__` на чистом Python, поскольку для этого требуется прямой доступ к C-структурам и управление выделением памяти объектов. Однако можно построить достаточно точную симуляцию, где реальная C-структура для слотов эмулируется частным списком `_slotvalues`. Чтение и запись в эту частную структуру управляются дескрипторами членов:977978```python979null = object()980981class Member:982983    def __init__(self, name, clsname, offset):984        'Emulate PyMemberDef in Include/structmember.h'985        # См. также descr_new() в Objects/descrobject.c986        self.name = name987        self.clsname = clsname988        self.offset = offset989990    def __get__(self, obj, objtype=None):991        'Emulate member_get() in Objects/descrobject.c'992        # См. также PyMember_GetOne() в Python/structmember.c993        value = obj._slotvalues[self.offset]994        if value is null:995            raise AttributeError(self.name)996        return value997998    def __set__(self, obj, value):999        'Emulate member_set() in Objects/descrobject.c'1000        obj._slotvalues[self.offset] = value10011002    def __delete__(self, obj):1003        'Emulate member_delete() in Objects/descrobject.c'1004        value = obj._slotvalues[self.offset]1005        if value is null:1006            raise AttributeError(self.name)1007        obj._slotvalues[self.offset] = null10081009    def __repr__(self):1010        'Emulate member_repr() in Objects/descrobject.c'1011        return f'<Member {self.name!r} of {self.clsname!r}>'1012```10131014Метод `type.__new__()` отвечает за добавление объектов-членов в переменные класса:10151016```python1017class Type(type):1018    'Simulate how the type metaclass adds member objects for slots'10191020    def __new__(mcls, clsname, bases, mapping):1021        'Emuluate type_new() in Objects/typeobject.c'1022        # type_new() вызывает PyTypeReady(), который вызывает add_methods()1023        slot_names = mapping.get('slot_names', [])1024        for offset, name in enumerate(slot_names):1025            mapping[name] = Member(name, clsname, offset)1026        return type.__new__(mcls, clsname, bases, mapping)1027```10281029Метод [`object.__new__()`](https://python-all.ru/3.9/reference/datamodel.html#object.__new__) отвечает за создание экземпляров, которые имеют слоты вместо словаря экземпляра. Вот примерная симуляция на чистом Python:10301031```python1032class Object:1033    'Simulate how object.__new__() allocates memory for __slots__'10341035    def __new__(cls, *args):1036        'Emulate object_new() in Objects/typeobject.c'1037        inst = super().__new__(cls)1038        if hasattr(cls, 'slot_names'):1039            empty_slots = [null] * len(cls.slot_names)1040            object.__setattr__(inst, '_slotvalues', empty_slots)1041        return inst10421043    def __setattr__(self, name, value):1044        'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'1045        cls = type(self)1046        if hasattr(cls, 'slot_names') and name not in cls.slot_names:1047            raise AttributeError(1048                f'{type(self).__name__!r} object has no attribute {name!r}'1049            )1050        super().__setattr__(name, value)10511052    def __delattr__(self, name):1053        'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c'1054        cls = type(self)1055        if hasattr(cls, 'slot_names') and name not in cls.slot_names:1056            raise AttributeError(1057                f'{type(self).__name__!r} object has no attribute {name!r}'1058            )1059        super().__delattr__(name)1060```10611062Чтобы использовать симуляцию в реальном классе, достаточно унаследоваться от `Object` и установить [метакласс](https://python-all.ru/3.9/glossary.html#term-metaclass) в `Type`:10631064```python1065class H(Object, metaclass=Type):1066    'Instance variables stored in slots'10671068    slot_names = ['x', 'y']10691070    def __init__(self, x, y):1071        self.x = x1072        self.y = y1073```10741075На этом этапе метакласс загрузил объекты-члены для *x* и *y*:10761077```python1078>>> from pprint import pp1079>>> pp(dict(vars(H)))1080{'__module__': '__main__',1081 '__doc__': 'Instance variables stored in slots',1082 'slot_names': ['x', 'y'],1083 '__init__': <function H.__init__ at 0x7fb5d302f9d0>,1084 'x': <Member 'x' of 'H'>,1085 'y': <Member 'y' of 'H'>}1086```10871088При создании экземпляров у них есть список `slot_values`, в котором хранятся атрибуты:10891090```pycon1091>>> h = H(10, 20)1092>>> vars(h)1093{'_slotvalues': [10, 20]}1094>>> h.x = 551095>>> vars(h)1096{'_slotvalues': [55, 20]}1097```10981099Атрибуты с ошибками в написании или не назначенные атрибуты вызовут исключение:11001101```pycon1102>>> h.xz1103Traceback (most recent call last):1104    ...1105AttributeError: 'H' object has no attribute 'xz'1106```1107