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

descriptor.md

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

1> **Источник:** https://python-all.ru/3.5/howto/descriptor.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# [Руководство по дескрипторам](https://python-all.ru/3.5/howto/descriptor.html#id1)89| Автор: | Рэймонд Хеттингер |10| --- | --- |11| Контакт: | \<python at rcn dot com\> |1213Содержание1415- [Руководство по дескрипторам](https://python-all.ru/3.5/howto/descriptor.html#descriptor-howto-guide)1617  - [Аннотация](https://python-all.ru/3.5/howto/descriptor.html#abstract)18  - [Определение и введение](https://python-all.ru/3.5/howto/descriptor.html#definition-and-introduction)19  - [Протокол дескрипторов](https://python-all.ru/3.5/howto/descriptor.html#descriptor-protocol)20  - [Вызов дескрипторов](https://python-all.ru/3.5/howto/descriptor.html#invoking-descriptors)21  - [Пример дескриптора](https://python-all.ru/3.5/howto/descriptor.html#descriptor-example)22  - [Свойства](https://python-all.ru/3.5/howto/descriptor.html#properties)23  - [Функции и методы](https://python-all.ru/3.5/howto/descriptor.html#functions-and-methods)24  - [Статические методы и методы класса](https://python-all.ru/3.5/howto/descriptor.html#static-methods-and-class-methods)2526## [Аннотация](https://python-all.ru/3.5/howto/descriptor.html#id2)2728Определяет дескрипторы, обобщает протокол и показывает, как вызываются дескрипторы. Рассматривает пользовательский дескриптор и несколько встроенных дескрипторов Python, включая функции, свойства, статические методы и методы класса. Показывает, как каждый из них работает, приводя эквивалент на чистом Python и пример приложения.2930Изучение дескрипторов не только даёт доступ к более широкому набору инструментов, но и позволяет глубже понять, как работает Python, и оценить элегантность его устройства.3132## [Определение и введение](https://python-all.ru/3.5/howto/descriptor.html#id3)3334В общем случае дескриптор – это атрибут объекта с «поведением связывания», доступ к которому переопределён методами протокола дескриптора. Такими методами являются [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__), [`__set__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__set__) и [`__delete__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__delete__). Если для объекта определён хотя бы один из этих методов, он считается дескриптором.3536Поведение по умолчанию при доступе к атрибуту – получение, установка или удаление атрибута из словаря объекта. Например, `a.x` имеет цепочку поиска, начинающуюся с `a.__dict__['x']`, затем `type(a).__dict__['x']`, и далее по базовым классам `type(a)`, исключая метаклассы. Если найденное значение является объектом, определяющим один из методов дескриптора, то Python может переопределить поведение по умолчанию и вместо этого вызвать метод дескриптора. Место в цепочке приоритетов зависит от того, какие методы дескриптора были определены.3738Дескрипторы – это мощный универсальный протокол. Они лежат в основе свойств, методов, статических методов, методов класса и [`super()`](https://python-all.ru/3.5/library/functions.html#super). Они используются во всём Python для реализации классов нового стиля, появившихся в версии 2.2. Дескрипторы упрощают внутренний C-код и предоставляют гибкий набор новых инструментов для повседневных программ на Python.3940## [Протокол дескриптора](https://python-all.ru/3.5/howto/descriptor.html#id4)4142`descr.__get__(self, obj, type=None) --> value`4344`descr.__set__(self, obj, value) --> None`4546`descr.__delete__(self, obj) --> None`4748Вот и всё. Определите любой из этих методов – и объект считается дескриптором и может переопределять поведение по умолчанию при поиске в качестве атрибута.4950Если объект определяет и [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__), и [`__set__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__set__), он считается data-дескриптором. Дескрипторы, определяющие только [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__), называются non-data-дескрипторами (обычно они используются для методов, но возможны и другие применения).5152Дескрипторы данных и не-данных различаются тем, как вычисляются переопределения по отношению к записям в словаре экземпляра. Если в словаре экземпляра есть запись с тем же именем, что и у дескриптора данных, дескриптор данных имеет приоритет. Если в словаре экземпляра есть запись с тем же именем, что и у дескриптора не-данных, приоритет имеет запись словаря.5354Чтобы создать дескриптор данных только для чтения, определите и [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__), и [`__set__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__set__), причём [`__set__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__set__) должен возбуждать [`AttributeError`](https://python-all.ru/3.5/library/exceptions.html#AttributeError) при вызове. Определения метода [`__set__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__set__) с заглушкой, возбуждающей исключение, достаточно, чтобы сделать его дескриптором данных.5556## [Вызов дескрипторов](https://python-all.ru/3.5/howto/descriptor.html#id5)5758Дескриптор можно вызвать напрямую по имени его метода. Например, `d.__get__(obj)`.5960Чаще, однако, дескриптор вызывается автоматически при доступе к атрибуту. Например, `obj.d` ищет `d` в словаре `obj`. Если `d` определяет метод [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__), то `d.__get__(obj)` вызывается в соответствии с правилами приоритета, приведёнными ниже.6162Детали вызова зависят от того, является ли `obj` объектом или классом.6364Для объектов механизм находится в [`object.__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__), который преобразует `b.x` в `type(b).__dict__['x'].__get__(b, type(b))`. Реализация работает через цепочку приоритетов, где дескрипторы данных имеют приоритет над переменными экземпляра, переменные экземпляра – над дескрипторами без данных, а самый низкий приоритет присваивается [`__getattr__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattr__), если он предоставлен. Полную реализацию на C можно найти в [`PyObject_GenericGetAttr()`](https://python-all.ru/3.5/c-api/object.html#c.PyObject_GenericGetAttr) в [Objects/object.c](https://python-all.ru/src/3.5/Objects/object.c).6566Для классов механизм находится в `type.__getattribute__()`, который преобразует `B.x` в `B.__dict__['x'].__get__(None, B)`. На чистом Python это выглядит так:6768```python69def __getattribute__(self, key):70    "Emulate type_getattro() in Objects/typeobject.c"71    v = object.__getattribute__(self, key)72    if hasattr(v, '__get__'):73        return v.__get__(None, self)74    return v75```7677Важно запомнить следующее:7879- дескрипторы вызываются методом [`__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__)80- переопределение [`__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__) предотвращает автоматический вызов дескрипторов81- [`object.__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__) и `type.__getattribute__()` выполняют разные вызовы [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__).82- дескрипторы данных всегда переопределяют словари экземпляров.83- дескрипторы без данных могут быть переопределены словарями экземпляров.8485Объект, возвращаемый `super()`, также имеет пользовательский [`__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__) метод для вызова дескрипторов. Вызов `super(B, obj).m()` ищет `obj.__class__.__mro__` базовый класс `A`, следующий сразу после `B` и затем возвращает `A.__dict__['m'].__get__(obj, B)`. Если это не дескриптор, `m` возвращается без изменений. Если его нет в словаре, `m` возвращается к поиску с помощью [`object.__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__).8687Детали реализации находятся в `super_getattro()` в [Objects/typeobject.c](https://python-all.ru/src/3.5/Objects/typeobject.c). Эквивалент на чистом Python можно найти в [руководстве Гвидо](https://python-all.ru/3.5/howto/descriptor.html).8889Приведённые выше детали показывают, что механизм дескрипторов встроен в методы [`__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__) для [`object`](https://python-all.ru/3.5/library/functions.html#object), [`type`](https://python-all.ru/3.5/library/functions.html#type) и [`super()`](https://python-all.ru/3.5/library/functions.html#super). Классы наследуют этот механизм, если они являются подклассами [`object`](https://python-all.ru/3.5/library/functions.html#object) или имеют метакласс, предоставляющий аналогичную функциональность. Классы также могут отключать вызов дескрипторов, переопределяя [`__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__).9091## [Пример дескриптора](https://python-all.ru/3.5/howto/descriptor.html#id6)9293Следующий код создаёт класс, объекты которого являются дескрипторами данных, выводящими сообщение при каждом получении или установке значения. Переопределение [`__getattribute__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__getattribute__) – это альтернативный подход, который мог бы делать это для каждого атрибута. Однако этот дескриптор удобен для отслеживания лишь нескольких выбранных атрибутов:9495```python96class RevealAccess(object):97    """Дескриптор данных, который обычным образом устанавливает и возвращает значения, а также выводит сообщение, регистрируя их доступ.98       обычным образом и выводит сообщение, регистрируя их доступ.99    """100101    def __init__(self, initval=None, name='var'):102        self.val = initval103        self.name = name104105    def __get__(self, obj, objtype):106        print('Retrieving', self.name)107        return self.val108109    def __set__(self, obj, val):110        print('Updating', self.name)111        self.val = val112113>>> class MyClass(object):114...     x = RevealAccess(10, 'var "x"')115...     y = 5116...117>>> m = MyClass()118>>> m.x119Retrieving var "x"12010121>>> m.x = 20122Updating var "x"123>>> m.x124Retrieving var "x"12520126>>> m.y1275128```129130Протокол прост и открывает захватывающие возможности. Некоторые сценарии использования настолько распространены, что были упакованы в отдельные вызовы функций. Свойства (properties), связанные (bound) и несвязанные (unbound) методы, статические методы и методы класса – все основаны на протоколе дескрипторов.131132## [Свойства](https://python-all.ru/3.5/howto/descriptor.html#id7)133134Вызов [`property()`](https://python-all.ru/3.5/library/functions.html#property) – это краткий способ создания дескриптора данных, который вызывает функции при доступе к атрибуту. Его сигнатура:135136```python137property(fget=None, fset=None, fdel=None, doc=None) -> property attribute138```139140В документации показан типичный пример использования для определения управляемого атрибута `x`:141142```python143class C(object):144    def getx(self): return self.__x145    def setx(self, value): self.__x = value146    def delx(self): del self.__x147    x = property(getx, setx, delx, "I'm the 'x' property.")148```149150Чтобы увидеть, как [`property()`](https://python-all.ru/3.5/library/functions.html#property) реализуется через протокол дескрипторов, вот эквивалент на чистом Python:151152```python153class Property(object):154    "Emulate PyProperty_Type() in Objects/descrobject.c"155156    def __init__(self, fget=None, fset=None, fdel=None, doc=None):157        self.fget = fget158        self.fset = fset159        self.fdel = fdel160        if doc is None and fget is not None:161            doc = fget.__doc__162        self.__doc__ = doc163164    def __get__(self, obj, objtype=None):165        if obj is None:166            return self167        if self.fget is None:168            raise AttributeError("unreadable attribute")169        return self.fget(obj)170171    def __set__(self, obj, value):172        if self.fset is None:173            raise AttributeError("can't set attribute")174        self.fset(obj, value)175176    def __delete__(self, obj):177        if self.fdel is None:178            raise AttributeError("can't delete attribute")179        self.fdel(obj)180181    def getter(self, fget):182        return type(self)(fget, self.fset, self.fdel, self.__doc__)183184    def setter(self, fset):185        return type(self)(self.fget, fset, self.fdel, self.__doc__)186187    def deleter(self, fdel):188        return type(self)(self.fget, self.fset, fdel, self.__doc__)189```190191Встроенная функция [`property()`](https://python-all.ru/3.5/library/functions.html#property) помогает, когда пользовательский интерфейс предоставил доступ к атрибуту, а последующие изменения требуют вмешательства метода.192193Например, класс электронной таблицы может предоставлять доступ к значению ячейки через `Cell('b10').value`. Последующие улучшения программы требуют пересчёта ячейки при каждом доступе; однако программист не хочет влиять на существующий клиентский код, обращающийся к атрибуту напрямую. Решение – обернуть доступ к атрибуту значения в дескриптор данных свойства:194195```python196class Cell(object):197    . . .198    def getvalue(self):199        "Recalculate the cell before returning value"200        self.recalc()201        return self._value202    value = property(getvalue)203```204205## [Функции и методы](https://python-all.ru/3.5/howto/descriptor.html#id8)206207Объектно-ориентированные возможности Python построены на функциональной основе. С помощью дескрипторов, не являющихся дескрипторами данных, эти две парадигмы объединяются бесшовно.208209Словари классов хранят методы как функции. В определении класса методы записываются с помощью [`def`](https://python-all.ru/3.5/reference/compound_stmts.html#def) и [`lambda`](https://python-all.ru/3.5/reference/expressions.html#lambda) – обычных инструментов для создания функций. Единственное отличие от обычных функций в том, что первый аргумент зарезервирован для экземпляра объекта. По соглашению Python ссылка на экземпляр называется *self*, но может называться *this* или любым другим именем переменной.210211Для поддержки вызовов методов функции включают метод [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__) для связывания методов при доступе к атрибуту. Это означает, что все функции являются дескрипторами не данных (non-data descriptors), которые возвращают связанные или несвязанные методы в зависимости от того, вызываются ли они от объекта или от класса. На чистом Python это работает так:212213```python214class Function(object):215    . . .216    def __get__(self, obj, objtype=None):217        "Simulate func_descr_get() in Objects/funcobject.c"218        return types.MethodType(self, obj)219```220221Запуск интерпретатора показывает, как дескриптор функции работает на практике:222223```python224>>> class D(object):225...     def f(self, x):226...         return x227...228>>> d = D()229>>> D.__dict__['f']  # Внутренне хранится как функция230<function f at 0x00C45070>231>>> D.f              # При получении из класса становится несвязанным методом232<unbound method D.f>233>>> d.f              # При получении из экземпляра становится связанным методом234<bound method D.f of <__main__.D object at 0x00B18C90>>235```236237Вывод предполагает, что связанные и несвязанные методы – это два разных типа. Хотя их можно было реализовать таким образом, фактическая C-реализация [`PyMethod_Type`](https://python-all.ru/3.5/c-api/method.html#c.PyMethod_Type) в [Objects/classobject.c](https://python-all.ru/src/3.5/Objects/classobject.c) представляет собой единый объект с двумя разными представлениями в зависимости от того, установлено ли поле `im_self` или равно *NULL* (C-эквивалент `None`).238239Аналогично, эффект вызова объекта метода зависит от поля `im_self`. Если оно установлено (то есть метод связан), исходная функция (хранящаяся в поле `im_func`) вызывается как обычно, с первым аргументом, установленным на экземпляр. Если не связано, все аргументы передаются исходной функции без изменений. Фактическая C-реализация `instancemethod_call()` лишь немного сложнее тем, что включает некоторую проверку типов.240241## [Статические методы и методы класса](https://python-all.ru/3.5/howto/descriptor.html#id9)242243Дескрипторы, не являющиеся дескрипторами данных, предоставляют простой механизм для вариаций обычных шаблонов привязки функций к методам.244245Повторим: функции имеют метод [`__get__()`](https://python-all.ru/3.5/reference/datamodel.html#object.__get__), чтобы их можно было преобразовать в метод при доступе как атрибуты. Дескриптор, не являющийся дескриптором данных, преобразует вызов `obj.f(*args)` в `f(obj, *args)`. Вызов `klass.f(*args)` становится `f(*args)`.246247Эта таблица обобщает привязку и два наиболее полезных её варианта:248249> | Преобразование | Вызов из объекта | Вызов из класса |250> | --- | --- | --- |251> | функция | f(obj, \*args) | f(\*args) |252> | staticmethod | f(\*args) | f(\*args) |253> | classmethod | f(type(obj), \*args) | f(klass, \*args) |254255Статические методы возвращают базовую функцию без изменений. Вызов `c.f` или `C.f` эквивалентен прямому поиску в `object.__getattribute__(c, "f")` или `object.__getattribute__(C, "f")`. В результате функция становится одинаково доступной как из объекта, так и из класса.256257Хорошими кандидатами для статических методов являются те, которые не ссылаются на переменную `self`.258259Например, в пакете статистики может быть класс-контейнер для экспериментальных данных. Класс предоставляет обычные методы для вычисления среднего, медианы и других описательных статистик, которые зависят от данных. Однако могут быть полезные функции, которые концептуально связаны, но не зависят от данных. Например, `erf(x)` – это удобная процедура преобразования, которая встречается в статистической работе, но не зависит напрямую от конкретного набора данных. Её можно вызвать как от объекта, так и от класса: `s.erf(1.5) --> .9332` или `Sample.erf(1.5) --> .9332`.260261Поскольку статические методы возвращают базовую функцию без изменений, примеры вызовов неинтересны:262263```python264>>> class E(object):265...     def f(x):266...         print(x)267...     f = staticmethod(f)268...269>>> print(E.f(3))2703271>>> print(E().f(3))2723273```274275Используя протокол дескриптора без данных, чистая версия [`staticmethod()`](https://python-all.ru/3.5/library/functions.html#staticmethod) на Python выглядела бы так:276277```python278class StaticMethod(object):279    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"280281    def __init__(self, f):282        self.f = f283284    def __get__(self, obj, objtype=None):285        return self.f286```287288В отличие от статических методов, методы класса перед вызовом функции добавляют ссылку на класс в начало списка аргументов. Этот формат одинаков как для вызова от объекта, так и от класса:289290```python291>>> class E(object):292...     def f(klass, x):293...         return klass.__name__, x294...     f = classmethod(f)295...296>>> print(E.f(3))297('E', 3)298>>> print(E().f(3))299('E', 3)300```301302Такое поведение полезно, когда функции нужна только ссылка на класс и не важны данные экземпляра. Одно из применений classmethod – создание альтернативных конструкторов класса. В Python 2.3 classmethod [`dict.fromkeys()`](https://python-all.ru/3.5/library/stdtypes.html#dict.fromkeys) создаёт новый словарь из списка ключей. Чистый эквивалент на Python выглядит так:303304```python305class Dict(object):306    . . .307    def fromkeys(klass, iterable, value=None):308        "Emulate dict_fromkeys() in Objects/dictobject.c"309        d = klass()310        for key in iterable:311            d[key] = value312        return d313    fromkeys = classmethod(fromkeys)314```315316Теперь новый словарь уникальных ключей можно создать так:317318```python319>>> Dict.fromkeys('abracadabra')320{'a': None, 'r': None, 'b': None, 'c': None, 'd': None}321```322323Используя протокол дескриптора без данных, чистая версия [`classmethod()`](https://python-all.ru/3.5/library/functions.html#classmethod) на Python выглядела бы так:324325```python326class ClassMethod(object):327    "Emulate PyClassMethod_Type() in Objects/funcobject.c"328329    def __init__(self, f):330        self.f = f331332    def __get__(self, obj, klass=None):333        if klass is None:334            klass = type(obj)335        def newfunc(*args):336            return self.f(klass, *args)337        return newfunc338```339