В этой статье я расскажу о том, что такое дескрипторы, о протоколе дескрипторов, покажу как вызываются дескрипторы. Опишу создание собственных и исследую несколько встроенных дескрипторов, включая функции, свойства, статические методы и методы класса. С помощью простого приложения покажу, как работает каждый из них, приведу эквиваленты внутренней реализации работы дескрипторов кодом на чистом питоне.
Изучение того, как работают дескрипторы, откроет доступ к большему числу рабочих инструментов, поможет лучше понять как работает питон, и ощутить элегантность его дизайна.
Введение и определения
Если говорить в общем, то дескриптор — это атрибут объекта со связанным поведением (англ. binding behavior), т.е. такой, чьё поведение при доступе переопределяется методами протокола дескриптора. Эти методы: __get__ , __set__ и __delete__ . Если хотя бы один из этих методов определён для объекта, то он становится дескриптором.
Стандартное поведение при доступе к атрибутам — это получение, установка и удаление атрибута из словаря объекта. Например, a.x имеет такую цепочку поиска атрибута: a.__dict__[‘x’] , затем в type(a).__dict__[‘x’] , и далее по базовым классам type(a) не включая метаклассы. Если же искомое значение — это объект, в котором есть хотя бы один из методов, определяющих дескриптор, то питон может изменить стандартную цепочку поиска и вызвать один из методов дескриптора. Как и когда это произойдёт зависит от того, какие методы дескриптора определены для объекта. Дескрипторы вызываются только для объектов или классов нового стиля (класс является таким, если наследует от object или type ).
Хранение файлов в Linux: Индексные дескрипторы (inodes)
Дескрипторы — это мощный протокол с широкой областью применения. Они являются тем механизмом, который стоит за свойствами, методами, статическими методами, методами класса и вызовом super() . Внутри самого питона с их помощью реализуются классы нового стиля, которые были представлены в версии 2.2. Дескрипторы упрощают понимание нижележащего кода на C, а также представляют гибкий набор новых инструментов для любых программ на питоне.
Протокол дескрипторов
descr.__get__(self, obj, type=None) —> значение descr.__set__(self, obj, value) —> None descr.__delete__(self, obj) —> None
Собственно это всё. Определите любой из этих методов и объект будет считаться дескриптором, и сможет переопределять стандартное поведение, если его будут искать как атрибут.
Если объект определяет сразу и __get__ , и __set__ , то он считается дескриптором данных (англ. data descriptor). Дескрипторы, которые определили только __get__ называются дескрипторами не данных (англ. non-data descriptors). Их называются так, потому что они используют для методов, но другие способы их применения также возможны.
Дескрипторы данных и не данных отличаются в том, как будет изменено поведение поиска, если в словаре объекта уже есть запись с таким же именем как у дескриптора. Если попадается дескриптор данных, то он вызывается раньше, чем запись из словаря объекта. Если в такой же ситуации окажется дескриптор не данных, то запись из словаря объекта имеет преимущество перед этим дескриптором.
Школа 21 — Файловый дескриптор
Чтобы создать дескриптор данных только для чтения, определите и __get__ , и __set__ , и сделайте так, чтобы __set__ выбрасывал исключение AttributeError . Определения метода __set__ и выбрасывания исключения достаточно, чтобы этот дескриптор считался дескриптором данных.
Вызов дескрипторов
Дескриптор можно вызвать напрямую через его метод. Например, d.__get__(obj) .
Однако, наиболее частый вариант вызова дескриптора — это автоматический вызов во время доступа к атрибуту. Например, obj.d ищет d в словаре obj . Если d определяет метод __get__ , то будет вызван d.__get__(obj) . Вызов будет сделан согласно правилам, описанным ниже.
Детали вызова различаются от того, чем является obj — объектом или классом. В любом случае, дескрипторы работают только для объектов и классов нового стиля. Класс является классом нового стиля, если он является потомком object .
Для объектов алгоритм реализуется с помощью object.__getattribute__ , который преобразует запись b.x в type(b).__dict__[‘x’].__get__(b, type(b)) . Реализация работает через цепочку предшественников, в которой дескрипторы данных имеют приоритет перед переменными объекта, переменные объекта имеют приоритет перед дескрипторами не данных, и самый низкий приоритет у метода __getattr__ , если он определён. Полную реализацию на языке C можно найти в PyObject_GenericGetAttr() в файле Objects/object.c .
Для классов алгоритм реализуется с помощью type.__getattribute__ , который преобразует запись B.x в B.__dict__[‘x’].__get__(None, B) . На чистом питоне это выглядит так:
def __getattribute__(self, key): «Эмуляция type_getattro() в Objects/typeobject.c» v = object.__getattribute__(self, key) if hasattr(v, ‘__get__’): return v.__get__(None, self) return v
- дескрипторы вызываются с помощью метода __getattribute__
- переопределение __getattribute__ прекратит автоматический вызов дескрипторов
- __getattribute__ доступен только внутри классов и объектов нового стиля
- object.__getattribute__ и type.__getattribute__ делают разные вызовы к __get__
- дескрипторы данных всегда имеют преимущество перед переменными объекта
- дескрипторы не данных могут потерять преимущество из-за переменных объекта
Примечание: в питоне 2.2, super(B, obj).m() вызывал __get__ только если m был дескриптором данных. В питоне 2.3, дескрипторы не данных тоже вызываются, за исключением тех случаев, когда используются классы старого стиля. Детали реализации можно найти в super_getattro() в файле Objects/typeobject.c , а эквивалент на чистом питоне можно найти в пособии от Guido.
Детали выше описывают, что алгоритм вызова дескрипторов реализуется с помощью метода __getattribute__() для object , type и super . Классы наследуют этот алгоритм, когда они наследуют от object или если у них есть метакласс, реализующий подобную функциональность. Таким образом, классы могут отключить вызов дескрипторов, если переопределят __getattribute__() .
Пример дескриптора
Следующий код создаёт класс, чьи объекты являются дескрипторам данных и всё, что они делают — это печатают сообщение на каждый вызов get или set . Переопределение __getattribute__ — это альтернативный подход, с помощью которого мы могли бы сделать это для каждого атрибута. Но если мы хотим наблюдать только за отдельными атрибутами, то это проще сделать с помощью дескриптора.
class RevealAccess(object): «»»Дескриптор данных, который устанавливает и возвращает значения, и печатает сообщение о том, что к атрибуту был доступ. «»» def __init__(self, initval=None, name=’var’): self.val = initval self.name = name def __get__(self, obj, objtype): print ‘Получаю’, self.name return self.val def __set__(self, obj, val): print ‘Обновляю’ , self.name self.val = val >>> class MyClass(object): x = RevealAccess(10, ‘var «x»‘) y = 5 >>> m = MyClass() >>> m.x Получаю var «x» 10 >>> m.x = 20 Обновляю var «x» >>> m.x Получаю var «x» 20 >>> m.y 5
Этот простой протокол предоставляет просто увлекательные возможности. Некоторые из них настолько часто используются, что были объединены в отдельные функции. Свойства, связанные и несвязанные методы, статические методы и методы класса — все они основаны на этом протоколе.
Свойства
Вызова property() достаточно, чтобы создать дескриптор данных, который вызывает нужные функции во время доступа к атрибуту. Вот его сигнатура:
property(fget=None, fset=None, fdel=None, doc=None) —> атрибут, реализующий свойства
В документации показано типичное использование property() для создания управляемого атрибута x :
class C(object): def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, «Я свойство ‘x’.»)
Вот эквивалент property на чистом питоне, чтобы было понятно как реализовано property() с помощью протокола дескрипторов:
class Property(object): «Эмуляция PyProperty_Type() в Objects/descrobject.c» def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError, «нечитаемый атрибут» return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError, «не могу установить атрибут» self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, «не могу удалить атрибут» self.fdel(obj)
Встроенная реализация property() может помочь, когда существовал интерфейс доступа к атрибуту и произошли какие-то изменения, в результате которых понадобилось вмешательство метода.
Например, класс электронной таблицы может давать доступ к значению ячейки через Cell(‘b10’).value . В результате последующих изменений в программе, понадобилось сделать так, чтобы это значение пересчитывалось при каждом доступе к ячейке, однако программист не хочет менять клиентский код, который обращается к атрибуту напрямую. Эту проблему можно решить, если обернуть атрибут value с помощью дескриптора данных, который будет создан с помощью property() :
class Cell(object): . . . def getvalue(self, obj): «Пересчитываем ячейку прежде чем вернуть значение» self.recalc() return obj._value value = property(getvalue)
Функции и методы
В питоне все объектно-ориентированные возможности реализованы с помощью функционального подхода. Это сделано совсем незаметно с помощью дескрипторов не данных.
Словари классов хранят методы в виде функций. При определении класса, методы записываются с помощью def и lambda — стандартных инструментов для создания функций. Единственное отличие этих функций от обычных в том, что первый аргумент зарезервирован под экземпляр объекта. Этот аргумент обычно называется self , но может называться this или любым другим словом, которым можно называть переменные.
Для того, чтобы поддерживать вызов методов, функции включают в себя метод __get__ , который автоматически делает их дескрипторами не данных при поиске атрибутов. Функции возвращают связанные или не связанные методы, в зависимости от того, через что был вызван этот дескриптор.
class Function(object): . . . def __get__(self, obj, objtype=None): «Симуляция func_descr_get() в Objects/funcobject.c» return types.MethodType(self, obj, objtype)
С помощью интерпретатора мы можем увидеть как на самом деле работает дескриптор функции:
>>> class D(object): def f(self, x): return x >>> d = D() >>> D.__dict__[‘f’] # Внутренне хранится как функция >>> D.f # Доступ через класс возвращает несвязанный метод >>> d.f # Доступ через экземпляр объекта возвращает связанный метод >
Вывод интерпретатора подсказывает нам, что связанные и несвязанные методы — это два разных типа. Даже если они могли бы быть реализованы таким образом, на самом деле, реализация PyMethod_Type в файле Objects/classobject.c содержит единственный объект с двумя различными отображениями, которые зависят только от того, есть ли в поле im_self значение или там содержится NULL (C эквивалент значения None ).
Таким образом, эффект вызова метода зависит от поля im_self . Если оно установлено (т.е. метод связан), то оригинальная функция (хранится в поле im_func ) вызывается, как мы и ожидаем, с первым аргументом, установленным в значение экземпляра объекта. Если же она не связана, то все аргументы передаются без изменения оригинальной функции. Настоящая C реализация instancemethod_call() чуть более сложная, потому что включает в себя некоторые проверки типов и тому подобное.
Статические методы и методы класса
Дескрипторы не данных предоставляют простой механизм для различных вариантов привязки функций к методам.
Повторим ещё раз. Функции имеют метод __get__ , с помощью которых они становятся методами, во время поиска атрибутов и автоматического вызова дескрипторов. Дескрипторы не данных преобразуют вызов obj.f(*args) в вызов f(obj, *args) , а вызов klass.f(*args) становится f(*args) .
В этой таблице показано связывание и два наиболее популярных варианта:
Дескриптор | функция | f(obj, *args) | f(*args) |
staticmethod | f(*args) | f(*args) | |
classmethod | f(type(obj), *args) | f(klass, *args) |
Статические методы возвращают функцию без изменений. Вызовы c.f или C.f эквиваленты вызовам object.__getattribute__(c, «f») или object.__getattribute__(C, «f») . Как результат, функция одинаково доступна как из объекта, так и из класса.
Хорошими кандидатами для статических методов являются методы, которым не нужна ссылка на переменную self .
Например, пакет для статистики может включать класс для экспериментальных данных. Класс предоставляет обычные методы для расчёта среднего, ожидания, медианы и другой статистики, которая зависит от данных. Однако, там могут быть и другие функции, которые концептуально связаны, но не зависят от данных. Например, erf(x) это простая функция для преобразования, которая нужна в статистике, но не зависит от конкретного набора данных в этом классе. Она может быть вызвана и из объекта, и из класса: s.erf(1.5) —> 0.9332 или Sample.erf(1.5) —> 0.9332 .
Так как staticmethod() возвращает функцию без изменений, то этот пример не удивляет:
>>> class E(object): def f(x): print x f = staticmethod(f) >>> print E.f(3) 3 >>> print E().f(3) 3
Если использовать протокол дескриптора не данных, то на чистом питоне staticmethod() выглядел бы так:
class StaticMethod(object): «Эмуляция PyStaticMethod_Type() в Objects/funcobject.c» def __init__(self, f): self.f = f def __get__(self, obj, objtype=None): return self.f
В отличие от статических методов, методы класса подставляют в начало вызова функции ссылку на класс. Формат вызова всегда один и тот же, и не зависит от того, вызываем мы метод через объект или через класс.
>>> class E(object): def f(klass, x): return klass.__name__, x f = classmethod(f) >>> print E.f(3) (‘E’, 3) >>> print E().f(3) (‘E’, 3)
Это поведение удобно, когда нашей функции всегда нужна ссылка на класс и ей не нужны данные. Один из способов использования classmethod() — это создание альтернативных конструкторов класса. В питоне 2.3, метод класса dict.fromkeys() создаёт новый словарь из списка ключей. Эквивалент на чистом питоне будет таким:
class Dict: . . . def fromkeys(klass, iterable, value=None): «Эмуляция dict_fromkeys() в Objects/dictobject.c» d = klass() for key in iterable: d[key] = value return d fromkeys = classmethod(fromkeys)
Теперь новый словарь уникальных ключей можно создать таким образом:
>>> Dict.fromkeys(‘abracadabra’)
Если использовать протокол дескриптора не данных, то на чистом питоне classmethod() выглядел бы так:
class ClassMethod(object): «Эмуляция PyClassMethod_Type() в Objects/funcobject.c» def __init__(self, f): self.f = f def __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.f(klass, *args) return newfunc
- python
- descriptor
- descriptor protocol
- питон
- дескриптор
- дескрипторы
- протокол дескрипторов
Источник: habr.com
Что это — дескриптор? Описание структуры атрибута
В общих чертах, дескрипторы — это некие атрибуты объектов, для которых определено соответствующее поведение. При доступе к ним происходит получение, установка или удаление указанных атрибутов из словаря. Говоря простым языком, это особые ключевые слова, определяющие поведение объекта, его свойства или структуру. В статье будет рассмотрено несколько вариантов дескрипторов. Это позволит более точно понять их суть и предназначение.
HTML
В языке разметки гипертекста, который применяется для создания статичных веб-страниц, дескрипторы — это не что иное, как тэги. В этой среде они выполняют роль меток, благодаря которым текст, размещённый между ними, отображается определенным способом. Таким образом и формируется дизайн и расположение элементов на странице.
Как правило, специфика языка определяет наличие открывающего дескриптора и закрывающего. Все, что между ними, подвержено изменению.
Дескриптор может иметь атрибуты, то есть определённые свойства. Их синтаксис выглядит так: имя атрибута = его значение.
Подчиняясь общей логике, дескрипторы в HTML представляют собой идентификационные метки объектов. С их помощью можно управлять внешним видом, взаимодействовать со страницей и присваивать ей особые значения.
Архитектура х86
В процессорах дескрипторы — это особая структура, с помощью которой совершаются различные переходы. Её строение выглядит так:
- Смещение. Может сдвинуть процедуру кода.
- Селектор. Отражает своего рода метку, куда должна перейти процедура.
- Количество параметров.
- Права доступа.
Java
В Java EE имеется понятие дескриптора развёртывания. Он определяет то, каким образом будет развёрнут сервлет. По сути, это конфигурационный файл, содержащий настройки, свойства и требования к аппаратной части.
В веб-приложениях такой файл по правилам должен называться web.xml и располагаться в определённой папке.
Файловые дескрипторы
В операционных системах для работы с файлами к ним сначала нужно получить доступ. Это можно осуществить через файловый дескриптор. В данном примере он выполняет роль своего рода метки, которой обозначается процесс, создавший поток ввода-вывода.
Операционная система Windows позволяет обратиться к дескриптору с помощью функции CreateFile. Его можно в дальнейшем использовать при работе с требуемым файлом.
В Unix системах для файлового дескриптора используется цифровые обозначения. 0 — стандартный ввод процесса терминала, 1 — его вывод, 2 — поток диагностики. Помимо числовых представлений можно использовать символические константы, определённые спецификацией.
Дескриптор окна
После того как в системе создаётся любое окно, ему присваивается определённый дескриптор. Типы данных применяемых в нем — HWND. Программисту, при создании приложения, нужно правильно использовать их для обращения к элементам окна.
Программа может получить дескриптор, обратившись к помощи функции FindWindow. Она по имени класса или объекта окна сможет его найти и вернуть значение дескриптора.
Чтобы проверить, правильно ли прошла идентификация, стоит использовать функцию IsWindow.
Ошибка номер 1400
Данная проблема часто встречается в семействе операционных систем Windows. При её появлении вместе с ней может быть и краткое описание — недопустимый дескриптор окна. А может встречаться и такая формулировка — «Error_invalid_window_handle» или 0х578.
Как бороться и что значит неверный дескриптор? В зависимости от контекста объекта, нужно принимать разные меры.
В любом случае стоит сразу же пройтись по стандартным мерам, принимаемым ко всем проблемам с системой:
- перезагрузка компьютера;
- запуск и сканирование ошибки с помощью утилиты проверки целостности системных файлов;
- проверить компьютер на вирусы.
Если базовые шаги не помогли, то, возможно, стоит удалить и снова установить приложение, которое вызывает сбой.
Ещё один надёжный способ быстро восстановить работоспособность системы — сделать её откат с помощью стандартных инструментов. После использования «Восстановления системы», она вернётся к последней действующей резервной копии, при условии что она была ранее создана.
Когда может возникнуть ошибка
В большинстве случаев ошибка неверного дескриптора вызывается из-за недоработок в установленном программном обеспечении. Поэтому стоит внимательно следить за тем, что из софта появляется на компьютере. Также необходимо периодически обновлять все имеющееся программное обеспечение и следить за его состоянием. При этом стоит вооружиться и хорошим антивирусным ПО.
Заключение
В статье были представлены общие принципы работы дескрипторов в различных языках программирования и операционных системах, их функции, свойства и виды. Как правило, данный термин в основном используется программистами и разработчиками для идентификации объектов и элементов для последующего обращения к ним и управления.
А также стало ясно, что это — неверный дескриптор. Как оказалось, это распространённая проблема в среде Windows, в большинстве случаев решаемая простыми манипуляциями.
Источник: fb.ru
Дескриптор версии 5.0
обработчика загрузки(729 КБ)
Введение
Вы когда-нибудь задавались вопросом, в какой программе открыт конкретный файл или каталог? Теперь вы можете узнать об этом. Handle — это служебная программа, которая отображает сведения об открытых дескрипторах для любого процесса в системе. Его можно использовать для просмотра программ, в которых открыт файл, или для просмотра типов объектов и имен всех дескрипторов программы.
Вы также можете получить версию этой программы на основе графического интерфейса , Process Explorer, здесь, в Sysinternals.
Установка
Запустите Handle , введя «handle». Для запуска Handle необходимо иметь права администратора.
Использование
Дескриптор предназначен для поиска открытых ссылок на файлы, поэтому, если не указать параметры командной строки, будут перечислены значения всех дескрипторов в системе, ссылающихся на открытые файлы, и имена файлов. Он также принимает несколько параметров, которые изменяют это поведение.
Использования: handle [[-a [-l]] [-v|-vt] [-u] | [-c [-y]] | [-s]] [-p |] [name]
-a | Дамп сведений обо всех типах дескрипторов, а не только о тех, которые ссылаются на файлы. К другим типам относятся порты, разделы реестра, примитивы синхронизации, потоки и процессы. |
-l | Просто показывать дескрипторы разделов с поддержкой файла подкачки. |
-c | Закрывает указанный дескриптор (интерпретируется как шестнадцатеричное число). Процесс необходимо указать по его PID. ПРЕДУПРЕЖДЕНИЕ: Закрытие дескрипторов может привести к нестабильной работе приложения или системы. |
-g | Доступ на печать предоставлен. |
-Y | Не запрашивайте подтверждение закрытия дескриптора. |
-s | Число распечаток каждого типа открытых дескрипторов. |
-u | Отображение имени пользователя-хладатора при поиске дескрипторов. |
-v | Выходные данные CSV с разделителями-запятыми. |
-Vt | Выходные данные CSV с разделителем табуляции. |
-p | Вместо проверки всех дескрипторов в системе этот параметр сужает сканирование Handle до тех процессов, которые начинаются с процесса имени. Таким образом: handle -p exp создает дамп открытых файлов для всех процессов, которые начинаются с «exp», в том числе проводника. |
name | Этот параметр присутствует, чтобы вы могли направить Handle на поиск ссылок на объект с определенным именем. Например, если вы хотите узнать, какой процесс (при наличии) открыт «c:windowssystem32», можно ввести: дескриптор windowssystem Имя не учитывает регистр, а указанный фрагмент может находиться в любом месте в интересующих вас путях. |
Обработка выходных данных
Если режим поиска не включен, указав фрагмент имени в качестве параметра, дескриптор делит выходные данные на разделы для каждого процесса, для которых печатается информация дескриптора. Пунктирные линии используются в качестве разделителя, сразу под которым вы увидите имя процесса и его идентификатор процесса (PID). Под именем процесса перечислены значения дескрипторов (в шестнадцатеричном формате), тип объекта, с которым связан дескриптор, и имя объекта, если он имеет его.
В режиме поиска Handle выводит имена процессов и идентификаторы в левой части, а имена объектов, для которых было совпадение, — справа.
Дополнительные сведения
Дополнительные сведения о диспетчере объектов можно найти во внутренних службах Windows, 4-м выпуске или в разделе Пространство имен диспетчера объектов с WinObj.
обработчика загрузки(729 КБ)
Источник: learn.microsoft.com