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

descriptor.md

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

1> **Источник:** https://python-all.ru/3.1/howto/descriptor.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# [Руководство по дескрипторам](https://python-all.ru/3.1/howto/descriptor.html#id1)89| Автор: | Рэймонд Хеттингер |10| --- | --- |11| Контакт: | \<python at rcn dot com\> |1213Содержание1415- [Руководство по дескрипторам](https://python-all.ru/3.1/howto/descriptor.html#descriptor-howto-guide)1617  - [Аннотация](https://python-all.ru/3.1/howto/descriptor.html#abstract)18  - [Определение и введение](https://python-all.ru/3.1/howto/descriptor.html#definition-and-introduction)19  - [Протокол дескрипторов](https://python-all.ru/3.1/howto/descriptor.html#descriptor-protocol)20  - [Вызов дескрипторов](https://python-all.ru/3.1/howto/descriptor.html#invoking-descriptors)21  - [Пример дескриптора](https://python-all.ru/3.1/howto/descriptor.html#descriptor-example)22  - [Свойства](https://python-all.ru/3.1/howto/descriptor.html#properties)23  - [Функции и методы](https://python-all.ru/3.1/howto/descriptor.html#functions-and-methods)24  - [Статические методы и методы класса](https://python-all.ru/3.1/howto/descriptor.html#static-methods-and-class-methods)2526## [Аннотация](https://python-all.ru/3.1/howto/descriptor.html#id2)2728Определяет дескрипторы, обобщает протокол и показывает, как вызываются дескрипторы. Рассматривает пользовательский дескриптор и несколько встроенных дескрипторов Python, включая функции, свойства, статические методы и методы класса. Показывает, как каждый из них работает, приводя эквивалент на чистом Python и пример приложения.2930Изучение дескрипторов не только даёт доступ к более широкому набору инструментов, но и позволяет глубже понять, как работает Python, и оценить элегантность его устройства.3132## [Определение и введение](https://python-all.ru/3.1/howto/descriptor.html#id3)3334В общем случае дескриптор – это атрибут объекта с «поведением привязки», доступ к которому переопределён методами протокола дескриптора. Такими методами являются [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__), [`__set__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__set__) и [`__delete__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__delete__). Если для объекта определён хотя бы один из этих методов, говорят, что он является дескриптором.3536Поведение по умолчанию при доступе к атрибуту заключается в получении, установке или удалении атрибута из словаря объекта. Например, `a.x` имеет цепочку поиска, начиная с `a.__dict__['x']`, затем `type(a).__dict__['x']`, и продолжая через базовые классы `type(a)`, исключая метаклассы. Если найденное значение является объектом, определяющим один из методов дескриптора, то Python может переопределить поведение по умолчанию и вместо этого вызвать метод дескриптора. То, где это происходит в цепочке приоритетов, зависит от того, какие методы дескриптора были определены. Обратите внимание, что дескрипторы вызываются только для объектов или классов нового стиля (класс является классом нового стиля, если он наследуется от [`object`](https://python-all.ru/3.1/library/functions.html#object) или [`type`](https://python-all.ru/3.1/library/functions.html#type)).3738Дескрипторы – это мощный универсальный протокол. Они лежат в основе свойств, методов, статических методов, методов класса и [`super()`](https://python-all.ru/3.1/library/functions.html#super). Они используются во всем Python для реализации классов нового стиля, представленных в версии 2.2. Дескрипторы упрощают внутренний C-код и предоставляют гибкий набор новых инструментов для повседневных программ на Python.3940## [Протокол дескриптора](https://python-all.ru/3.1/howto/descriptor.html#id4)4142`descr.__get__(self, obj, type=None) --> значение`4344`descr.__set__(self, obj, value) --> None`4546`descr.__delete__(self, obj) --> None`4748Вот и всё. Определите любой из этих методов – и объект считается дескриптором и может переопределять поведение по умолчанию при поиске в качестве атрибута.4950Если объект определяет и [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__), и [`__set__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__set__), он считается дескриптором данных. Дескрипторы, определяющие только [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__), называются дескрипторами не-данных (обычно они используются для методов, но возможны и другие применения).5152Дескрипторы данных и не-данных различаются тем, как вычисляются переопределения по отношению к записям в словаре экземпляра. Если в словаре экземпляра есть запись с тем же именем, что и у дескриптора данных, дескриптор данных имеет приоритет. Если в словаре экземпляра есть запись с тем же именем, что и у дескриптора не-данных, приоритет имеет запись словаря.5354Чтобы сделать дескриптор данных доступным только для чтения, определите и [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__), и [`__set__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__set__), причём [`__set__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__set__) должен возбуждать [`AttributeError`](https://python-all.ru/3.1/library/exceptions.html#AttributeError) при вызове. Определения [`__set__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__set__) с заглушкой, возбуждающей исключение, достаточно, чтобы сделать его дескриптором данных.5556## [Вызов дескрипторов](https://python-all.ru/3.1/howto/descriptor.html#id5)5758Дескриптор можно вызвать напрямую по имени метода. Например, `d.__get__(obj)`.5960Но чаще дескриптор вызывается автоматически при доступе к атрибуту. Например, `obj.d` ищет `d` в словаре `obj`. Если `d` определяет метод [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__), то вызывается `d.__get__(obj)` в соответствии с правилами приоритета, приведёнными ниже.6162Подробности вызова зависят от того, является ли `obj` объектом или классом. В любом случае дескрипторы работают только с объектами и классами нового стиля. Класс является классом нового стиля, если он является подклассом [`object`](https://python-all.ru/3.1/library/functions.html#object).6364Для объектов механизм находится в [`object.__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__), который преобразует `b.x` в `type(b).__dict__['x'].__get__(b, type(b))`. Реализация работает по цепочке приоритетов, где дескрипторы данных имеют приоритет над переменными экземпляра, переменные экземпляра – над дескрипторами не-данных, а самый низкий приоритет у [`__getattr__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattr__), если он определён. Полную реализацию на C можно найти в [`PyObject_GenericGetAttr()`](https://python-all.ru/3.1/c-api/object.html#PyObject_GenericGetAttr) в файле [Objects/object.c](https://python-all.ru/3.1/howto/descriptor.html).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.1/reference/datamodel.html#object.__getattribute__)80- переопределение [`__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__) предотвращает автоматический вызов дескрипторов81- [`__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__) доступен только для классов и объектов нового стиля82- [`object.__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__) и `type.__getattribute__()` по-разному вызывают [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__).83- дескрипторы данных всегда переопределяют словари экземпляров.84- дескрипторы без данных могут быть переопределены словарями экземпляров.8586Объект, возвращаемый `super()`, также имеет собственный метод [`__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__) для вызова дескрипторов. Вызов `super(B, obj).m()` ищет в `obj.__class__.__mro__` базовый класс `A`, следующий непосредственно за `B`, и затем возвращает `A.__dict__['m'].__get__(obj, A)`. Если это не дескриптор, `m` возвращается без изменений. Если его нет в словаре, `m` возвращается к поиску с помощью [`object.__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__).8788Обратите внимание: в Python 2.2 `super(B, obj).m()` вызывал бы [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__), только если `m` был дескриптором данных. В Python 2.3 дескрипторы не данных также вызываются, если только не задействован класс старого стиля. Подробности реализации находятся в `super_getattro()` в [Objects/typeobject.c](https://python-all.ru/3.1/howto/descriptor.html), а чистый Python-эквивалент можно найти в [руководстве Гвидо](https://python-all.ru/3.1/howto/descriptor.html).8990Из изложенного видно, что механизм дескрипторов встроен в методы [`__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__) для [`object`](https://python-all.ru/3.1/library/functions.html#object), [`type`](https://python-all.ru/3.1/library/functions.html#type) и [`super()`](https://python-all.ru/3.1/library/functions.html#super). Классы наследуют этот механизм, если они производны от [`object`](https://python-all.ru/3.1/library/functions.html#object) или имеют метакласс, предоставляющий аналогичную функциональность. Классы также могут отключить вызов дескрипторов, переопределив [`__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__).9192## [Пример дескриптора](https://python-all.ru/3.1/howto/descriptor.html#id6)9394Следующий код создаёт класс, объекты которого являются дескрипторами данных, выводящими сообщение при каждом get или set. Переопределение [`__getattribute__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__getattribute__) – это альтернативный подход, который мог бы делать это для каждого атрибута. Однако этот дескриптор удобен для мониторинга лишь нескольких выбранных атрибутов:9596```python97class RevealAccess(object):98    """Дескриптор данных, который обычным образом устанавливает и возвращает значения, а также выводит сообщение, регистрируя их доступ.99       обычным образом и выводит сообщение, регистрируя их доступ.100    """101102    def __init__(self, initval=None, name='var'):103        self.val = initval104        self.name = name105106    def __get__(self, obj, objtype):107        print('Retrieving', self.name)108        return self.val109110    def __set__(self, obj, val):111        print('Updating', self.name)112        self.val = val113114>>> class MyClass(object):115    x = RevealAccess(10, 'var "x"')116    y = 5117118>>> m = MyClass()119>>> m.x120Retrieving var "x"12110122>>> m.x = 20123Updating var "x"124>>> m.x125Retrieving var "x"12620127>>> m.y1285129```130131Протокол прост и открывает захватывающие возможности. Некоторые сценарии использования настолько распространены, что были упакованы в отдельные вызовы функций. Свойства (properties), связанные (bound) и несвязанные (unbound) методы, статические методы и методы класса – все основаны на протоколе дескрипторов.132133## [Свойства](https://python-all.ru/3.1/howto/descriptor.html#id7)134135Вызов [`property()`](https://python-all.ru/3.1/library/functions.html#property) – это краткий способ создания дескриптора данных, который запускает вызовы функций при доступе к атрибуту. Его сигнатура:136137```python138property(fget=None, fset=None, fdel=None, doc=None) -> property attribute139```140141В документации показан типичный пример использования для определения управляемого атрибута `x`:142143```python144class C(object):145    def getx(self): return self.__x146    def setx(self, value): self.__x = value147    def delx(self): del self.__x148    x = property(getx, setx, delx, "I'm the 'x' property.")149```150151Чтобы увидеть, как [`property()`](https://python-all.ru/3.1/library/functions.html#property) реализован на основе протокола дескрипторов, вот эквивалент на чистом Python:152153```python154class Property(object):155    "Emulate PyProperty_Type() in Objects/descrobject.c"156157    def __init__(self, fget=None, fset=None, fdel=None, doc=None):158        self.fget = fget159        self.fset = fset160        self.fdel = fdel161        self.__doc__ = doc162163    def __get__(self, obj, objtype=None):164        if obj is None:165            return self166        if self.fget is None:167            raise AttributeError, "unreadable attribute"168        return self.fget(obj)169170    def __set__(self, obj, value):171        if self.fset is None:172            raise AttributeError, "can't set attribute"173        self.fset(obj, value)174175    def __delete__(self, obj):176        if self.fdel is None:177            raise AttributeError, "can't delete attribute"178        self.fdel(obj)179```180181Встроенная функция [`property()`](https://python-all.ru/3.1/library/functions.html#property) полезна всякий раз, когда пользовательский интерфейс предоставил доступ к атрибуту, а последующие изменения потребовали вмешательства метода.182183Например, класс электронной таблицы может предоставлять доступ к значению ячейки через `Cell('b10').value`. Последующие улучшения программы требуют пересчитывать ячейку при каждом доступе; однако программист не хочет затрагивать существующий клиентский код, обращающийся к атрибуту напрямую. Решение – обернуть доступ к атрибуту value в дескриптор данных property:184185```python186class Cell(object):187    . . .188    def getvalue(self, obj):189        "Recalculate cell before returning value"190        self.recalc()191        return obj._value192    value = property(getvalue)193```194195## [Функции и методы](https://python-all.ru/3.1/howto/descriptor.html#id8)196197Объектно-ориентированные возможности Python построены на функциональной основе. С помощью дескрипторов, не являющихся дескрипторами данных, эти две парадигмы объединяются бесшовно.198199Словари классов хранят методы как функции. В определении класса методы записываются с помощью [`def`](https://python-all.ru/3.1/reference/compound_stmts.html#def) и [`lambda`](https://python-all.ru/3.1/reference/expressions.html#lambda) – обычных инструментов для создания функций. Единственное отличие от обычных функций в том, что первый аргумент зарезервирован для экземпляра объекта. По соглашению Python ссылка на экземпляр называется *self*, но может называться *this* или любым другим именем переменной.200201Для поддержки вызовов методов функции включают метод [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__) для привязки методов при доступе к атрибуту. Это означает, что все функции являются дескрипторами не-данных, которые возвращают связанные или несвязанные методы в зависимости от того, вызываются ли они от объекта или от класса. На чистом Python это работает так:202203```python204class Function(object):205    . . .206    def __get__(self, obj, objtype=None):207        "Simulate func_descr_get() in Objects/funcobject.c"208        return types.MethodType(self, obj, objtype)209```210211Запуск интерпретатора показывает, как дескриптор функции работает на практике:212213```python214>>> class D(object):215     def f(self, x):216          return x217218>>> d = D()219>>> D.__dict__['f'] # Внутренне хранится как функция220<function f at 0x00C45070>221>>> D.f             # При получении из класса становится несвязанным методом222<unbound method D.f>223>>> d.f             # При получении из экземпляра становится связанным методом224<bound method D.f of <__main__.D object at 0x00B18C90>>225```226227Вывод показывает, что связанные и несвязанные методы – это два разных типа. Хотя их можно было бы так реализовать, реальная реализация на C [`PyMethod_Type`](https://python-all.ru/3.1/c-api/method.html#PyMethod_Type) в файле [Objects/classobject.c](https://python-all.ru/3.1/howto/descriptor.html) представляет собой один объект с двумя разными представлениями в зависимости от того, установлено ли поле `im_self` или равно *NULL* (эквивалент *None* на C).228229Аналогично, результат вызова объекта метода зависит от поля `im_self`. Если оно установлено (то есть метод связан), исходная функция (хранящаяся в поле `im_func`) вызывается как обычно, с первым аргументом, равным экземпляру. Если метод не связан, все аргументы передаются исходной функции без изменений. Реальная C-реализация `instancemethod_call()` лишь немного сложнее, так как включает некоторую проверку типов.230231## [Статические методы и методы класса](https://python-all.ru/3.1/howto/descriptor.html#id9)232233Дескрипторы, не являющиеся дескрипторами данных, предоставляют простой механизм для вариаций обычных шаблонов привязки функций к методам.234235Итак, функции имеют метод [`__get__()`](https://python-all.ru/3.1/reference/datamodel.html#object.__get__), чтобы их можно было преобразовать в метод при доступе в качестве атрибута. Дескриптор не-данных преобразует вызов `obj.f(*args)` в `f(obj, *args)`. Вызов `klass.f(*args)` становится вызовом `f(*args)`.236237Эта таблица обобщает привязку и два наиболее полезных её варианта:238239> | Преобразование | Вызов из объекта | Вызов из класса |240> | --- | --- | --- |241> | функция | f(obj, \*args) | f(\*args) |242> | staticmethod | f(\*args) | f(\*args) |243> | classmethod | f(type(obj), \*args) | f(klass, \*args) |244245Статические методы возвращают базовую функцию без изменений. Вызов `c.f` или `C.f` эквивалентен прямому поиску в `object.__getattribute__(c, "f")` или `object.__getattribute__(C, "f")`. В результате функция становится одинаково доступной как из объекта, так и из класса.246247Хорошими кандидатами для статических методов являются методы, не ссылающиеся на переменную `self`.248249Например, пакет статистики может содержать класс-контейнер для экспериментальных данных. Класс предоставляет обычные методы для вычисления среднего, среднего арифметического, медианы и других описательных статистик, которые зависят от данных. Однако, могут быть полезные функции, которые концептуально связаны, но не зависят от данных. Например, `erf(x)` – это удобная функция преобразования, которая встречается в статистической работе, но напрямую не зависит от конкретного набора данных. Её можно вызвать как из объекта, так и из класса: `s.erf(1.5) --> .9332` или `Sample.erf(1.5) --> .9332`.250251Поскольку статические методы возвращают базовую функцию без изменений, примеры вызовов неинтересны:252253```python254>>> class E(object):255     def f(x):256          print(x)257     f = staticmethod(f)258259>>> print(E.f(3))2603261>>> print(E().f(3))2623263```264265С использованием протокола дескриптора не данных, чистая Python-версия [`staticmethod()`](https://python-all.ru/3.1/library/functions.html#staticmethod) выглядела бы так:266267```python268class StaticMethod(object):269 "Emulate PyStaticMethod_Type() in Objects/funcobject.c"270271 def __init__(self, f):272      self.f = f273274 def __get__(self, obj, objtype=None):275      return self.f276```277278В отличие от статических методов, методы класса перед вызовом функции добавляют ссылку на класс в начало списка аргументов. Этот формат одинаков как для вызова от объекта, так и от класса:279280```python281>>> class E(object):282     def f(klass, x):283          return klass.__name__, x284     f = classmethod(f)285286>>> print(E.f(3))287('E', 3)288>>> print(E().f(3))289('E', 3)290```291292Такое поведение полезно, когда функции нужна только ссылка на класс и ей не важны какие-либо базовые данные. Одно из применений classmethod – создание альтернативных конструкторов класса. В Python 2.3 classmethod [`dict.fromkeys()`](https://python-all.ru/3.1/library/stdtypes.html#dict.fromkeys) создаёт новый словарь из списка ключей. Чистая реализация на Python выглядит так:293294```python295class Dict:296    . . .297    def fromkeys(klass, iterable, value=None):298        "Emulate dict_fromkeys() in Objects/dictobject.c"299        d = klass()300        for key in iterable:301            d[key] = value302        return d303    fromkeys = classmethod(fromkeys)304```305306Теперь новый словарь уникальных ключей можно создать так:307308```python309>>> Dict.fromkeys('abracadabra')310{'a': None, 'r': None, 'b': None, 'c': None, 'd': None}311```312313С использованием протокола дескриптора не данных, чистая Python-версия [`classmethod()`](https://python-all.ru/3.1/library/functions.html#classmethod) выглядела бы так:314315```python316class ClassMethod(object):317     "Emulate PyClassMethod_Type() in Objects/funcobject.c"318319     def __init__(self, f):320          self.f = f321322     def __get__(self, obj, klass=None):323          if klass is None:324               klass = type(obj)325          def newfunc(*args):326               return self.f(klass, *args)327          return newfunc328```329