Дескриптор — что это такое и зачем он нужен в программировании

Дескриптор — это не один конкретный объект, а общий технический термин для сущности, которая описывает ресурс или даёт программе управляемый доступ к нему. В Linux и других Unix-подобных системах чаще всего говорят о файловых дескрипторах, в Windows близкую роль играют handles, а в Python дескриптором называют механизм управления доступом к атрибутам объектов.

Дескриптор — что это такое и зачем он нужен
Дескриптор — что это такое и зачем он нужен

Дескриптор простыми словами

Слово descriptor буквально можно перевести как «описатель». В программировании оно обычно означает небольшой идентификатор, структуру или объект, через который программа обращается к чему-то более сложному.

Удобная аналогия — номерок в гардеробе. Сам номерок не является курткой и не содержит её внутри. Он лишь позволяет системе понять, какой именно объект связан с владельцем номерка. Примерно так же программа может хранить не весь системный объект, а компактную ссылку на него.

Из-за этого термин «дескриптор» встречается в разных контекстах и означает немного разные вещи.

КонтекстЧто называют дескрипторомДля чего он нужен
Linux / UnixНебольшое целое числоОбращение к открытому файлу, сокету, каналу и другим ресурсам
WindowsОбычно используется термин handleДоступ к файлам, процессам, потокам, событиям и другим объектам ОС
PythonОбъект с __get__(), __set__() или __delete__()Управление чтением, записью и удалением атрибутов
Системное программированиеИдентификатор или структура, описывающая ресурсПередача ресурса между уровнями программы без прямого доступа к его внутреннему устройству

Поэтому фраза «дескриптор — это число» верна только в определённом контексте. Для Unix-файлового дескриптора — да. Для Python-дескриптора — уже нет.

Файловый дескриптор в Linux и Unix

В Linux файловый дескриптор — это небольшое неотрицательное целое число, которое служит индексом в таблице открытых дескрипторов конкретного процесса.

Когда программа вызывает open(), ядро открывает файл и возвращает программе номер. Дальнейшие операции read(), write(), lseek() и другие используют именно этот номер.

Упрощённо цепочка выглядит так:

Программа
   ↓
fd = 3
   ↓
Таблица файловых дескрипторов процесса
   ↓
Описание открытого файла
   ↓
Файл / сокет / канал / устройство

Например:

int fd = open("example.txt", O_RDONLY);

Если вызов прошёл успешно, переменная fd может получить значение 3.

Число 3 не является адресом файла на диске. Оно лишь сообщает ядру, к какой записи таблицы открытых ресурсов относится следующий системный вызов.

Почему часто встречаются 0, 1 и 2

В Unix-подобных системах первые три файловых дескриптора традиционно заняты стандартными потоками:

ДескрипторНазначение
0стандартный ввод — stdin
1стандартный вывод — stdout
2стандартный поток ошибок — stderr

Поэтому первый файл, который программа открывает самостоятельно, часто получает дескриптор 3, если до этого другие ресурсы не были открыты.

Это хорошо видно в командной строке Linux. Конструкция:

command > output.txt

перенаправляет стандартный вывод программы, то есть файловый дескриптор 1, в файл output.txt.

А команда:

command 2> errors.txt

направляет поток ошибок с дескриптора 2 в отдельный файл.

Дескриптор — это не обязательно файл

Название «файловый дескриптор» иногда сбивает с толку. В Unix-подобных системах через один и тот же интерфейс могут обслуживаться разные типы ресурсов.

Файловый дескриптор может ссылаться, например, на:

  • обычный файл;
  • каталог;
  • сетевой сокет;
  • pipe;
  • FIFO;
  • терминал;
  • устройство;
  • некоторые специальные интерфейсы ядра.

Именно поэтому сетевой сервер в Linux может использовать функции вроде select(), poll() или epoll() для работы сразу с большим количеством соединений: каждое открытое соединение представлено файловым дескриптором.

Операционная система скрывает внутреннее устройство ресурса, а приложение взаимодействует с ним через унифицированный идентификатор.

Что происходит после open()

В Linux вызов open() создаёт не просто число.

Ядро формирует запись, которую документация Linux называет open file description. В ней хранятся сведения об открытом ресурсе, в том числе текущая позиция чтения и некоторые флаги состояния.

Файловый дескриптор процесса ссылается на эту запись.

Это различие важно.

fd 3 ──┐
       ├──> open file description ──> файл
fd 7 ──┘

Два разных дескриптора могут ссылаться на одно и то же описание открытого файла. Например, такое происходит после dup() или при наследовании открытых дескрипторов дочерним процессом.

В этом случае дескрипторы разные, но состояние открытого файла частично общее — например, они могут использовать одну текущую позицию чтения.

Почему дескрипторы нужно закрывать

После завершения работы с ресурсом файловый дескриптор обычно закрывают:

close(fd);

У процесса есть ограничение на количество одновременно открытых файловых дескрипторов. Если программа постоянно открывает файлы или соединения и не закрывает их, возникает утечка дескрипторов.

Типичный сценарий:

1. Сервер принимает соединение.
2. Для него создаётся новый файловый дескриптор.
3. Клиент отключается.
4. Программа забывает закрыть fd.
5. Ситуация повторяется тысячи раз.
6. Процесс достигает лимита открытых дескрипторов.
7. Новые файлы или соединения открыть уже нельзя.

В Linux подобная ошибка часто проявляется сообщением:

Too many open files

Проверить открытые файловые дескрипторы процесса можно через каталог:

/proc/<PID>/fd/

Например:

ls -l /proc/1234/fd/

В нём будут записи 0, 1, 2, 3 и далее, соответствующие открытым дескрипторам процесса.

Файловый дескриптор и handle в Windows — в чём разница

В Windows чаще используется термин handle — «дескриптор» или «описатель объекта» в русскоязычной технической литературе.

Handle позволяет приложению обращаться к системному объекту через непрозрачное значение типа HANDLE. Такой объект может представлять:

  • файл;
  • процесс;
  • поток;
  • событие;
  • ключ реестра;
  • объект синхронизации;
  • другие ресурсы Windows.

Логика похожа на Unix:

Программа → handle → системный объект

Но считать Windows handle и Unix file descriptor полностью одинаковыми нельзя.

Unix-файловый дескриптор обычно представляет собой небольшое целое число, индексирующее таблицу открытых дескрипторов процесса. Windows API использует непрозрачный тип HANDLE, внутреннее значение которого приложение не должно трактовать самостоятельно.

Для разработчика принцип один: операционная система отдаёт программе управляемую ссылку на ресурс, а сама внутренняя структура объекта остаётся скрытой.

Что такое дескриптор в Python

В Python слово «дескриптор» означает уже другой механизм.

Согласно официальной документации Python, дескриптором считается объект, который определяет хотя бы один из методов:

__get__()
__set__()
__delete__()

Такой объект может перехватывать доступ к атрибуту класса.

Простейший пример:

class Positive:
    def __get__(self, obj, objtype=None):
        return obj._value

    def __set__(self, obj, value):
        if value < 0:
            raise ValueError("Значение не может быть отрицательным")
        obj._value = value


class Product:
    price = Positive()

    def __init__(self, price):
        self.price = price

Теперь:

item = Product(100)
print(item.price)

вернёт 100.

А попытка:

item.price = -50

вызовет исключение.

Дескриптор в этом случае становится промежуточным слоем между выражением:

item.price

и фактическим хранением значения.

Где Python использует дескрипторы внутри языка

Дескрипторный протокол — не редкая экзотика. На нём основаны привычные механизмы Python.

К ним относятся:

  • методы объектов;
  • property;
  • classmethod;
  • staticmethod;
  • super();
  • часть поведения __slots__.

Например:

class User:
    @property
    def name(self):
        return self._name

property работает как дескриптор и определяет, что именно произойдёт при обращении к user.name.

Поэтому изучение дескрипторов особенно полезно при разработке библиотек, ORM, систем валидации, фреймворков и собственных механизмов управления атрибутами.

Data descriptor и non-data descriptor в Python

Python различает два основных типа дескрипторов.

Data descriptor

Объект считается data descriptor, если определяет __set__() или __delete__().

Например:

class Field:
    def __get__(self, obj, objtype=None):
        ...

    def __set__(self, obj, value):
        ...

Такой дескриптор имеет более высокий приоритет при поиске атрибута, чем одноимённое значение в словаре экземпляра.

Non-data descriptor

Если объект определяет только __get__(), он относится к non-data descriptors.

Обычные методы класса — один из наиболее известных примеров такого поведения.

Разница становится важной, когда у экземпляра и класса существуют одноимённые атрибуты: Python использует установленный порядок поиска, а тип дескриптора влияет на то, какое значение победит.

Почему одно слово означает разные вещи

Все варианты термина объединяет одна идея: дескриптор находится между программой и объектом, к которому она обращается.

Но форма этого посредника зависит от системы.

Linux:
процесс → число fd → открытый ресурс

Windows:
процесс → HANDLE → объект ОС

Python:
экземпляр → атрибут класса-дескриптор → логика чтения/записи

Поэтому при чтении документации сначала нужно понять контекст.

Фраза:

«Закройте дескриптор после использования»

скорее всего относится к системному ресурсу.

А фраза:

«Реализуйте метод __get__() в дескрипторе»

однозначно относится к Python.

Дескриптор, указатель и идентификатор — не одно и то же

Эти термины часто смешивают, хотя они решают разные задачи.

ТерминЧто представляетМожно ли напрямую обращаться к объекту
ДескрипторУправляемую ссылку или механизм доступаОбычно нет, доступ идёт через API или протокол
УказательАдрес или значение, позволяющее обратиться к области памятиДа, в языках с соответствующей моделью памяти
IDУникальный или условно уникальный номер объектаЗависит от системы
HandleНепрозрачную ссылку на объект, особенно в Windows APIЧерез функции API

У файлового дескриптора число 3 само по себе ничего не говорит о расположении файла в памяти или на диске. Оно имеет смысл только внутри соответствующего процесса и его таблицы открытых дескрипторов.

Это одно из главных отличий дескриптора от указателя.

Где разработчик сталкивается с дескрипторами на практике

Даже если приложение написано на языке высокого уровня, дескрипторы постоянно работают под капотом.

В серверной разработке

Каждое сетевое соединение может занимать файловый дескриптор. Поэтому для высоконагруженного сервера имеют значение системные лимиты и корректное закрытие сокетов.

При работе с файлами

Открытый файл в Python, C, Go, Rust или другом языке в конечном счёте связан с механизмами операционной системы. Высокоуровневый объект скрывает низкоуровневый файловый дескриптор, но сам ресурс остаётся системным.

При диагностике утечек

Если приложение постепенно перестаёт открывать файлы или принимать новые соединения, одной из причин может быть утечка файловых дескрипторов.

При разработке на Python

Дескрипторы позволяют создать единое правило для множества атрибутов — например, проверять тип, диапазон значения, формат строки или автоматически преобразовывать данные.

Вместо повторения одинаковой логики в десятках классов её можно вынести в один дескриптор.

Частые ошибки в понимании термина

«Дескриптор — это адрес объекта»

Не обязательно. Unix-файловый дескриптор — индекс в таблице процесса, а не адрес объекта в памяти.

«Дескриптор всегда является числом»

Нет. Файловый дескриптор Unix — число, но Python-дескриптор — объект, реализующий специальный протокол.

«Файловый дескриптор бывает только у файлов»

Нет. В Unix-подобной системе файловые дескрипторы используются и для сокетов, каналов и ряда других ресурсов.

«Windows handle и Linux fd полностью одинаковы»

Нет. Они решают похожую задачу — дают программе ссылку на системный ресурс, — но реализованы по-разному и используются через разные API.

«О закрытии дескриптора можно не думать»

Для ресурсов операционной системы это опасная практика. Незакрытые дескрипторы могут накапливаться и исчерпать лимит процесса.

Как запомнить смысл дескриптора

Самая полезная формулировка:

Дескриптор — это посредник между кодом и некоторым объектом или ресурсом.

Дальше нужно только уточнить контекст.

Если речь о Linux — чаще всего это номер открытого ресурса.

Если о Windows — обычно имеется в виду handle.

Если о Python — объект, управляющий поведением атрибута.

Такое определение точнее, чем попытка свести все варианты термина к одному типу данных.

Что нужно понимать о дескрипторах

Дескриптор не имеет единственного универсального определения для всех языков и операционных систем. Общий смысл термина — предоставить программе контролируемый способ идентифицировать ресурс, описывать его или управлять доступом к нему.

Для системного программирования особенно важно различать три случая:

  1. Unix/Linux: файловый дескриптор — небольшой неотрицательный целочисленный идентификатор открытого ресурса внутри процесса.
  2. Windows: для доступа к системным объектам широко используются handles — непрозрачные значения, передаваемые функциям Windows API.
  3. Python: descriptor — объект с __get__(), __set__() или __delete__(), который может изменять стандартное поведение доступа к атрибутам.

Если в документации встречается слово «дескриптор», его смысл почти всегда можно определить по соседним терминам: open(), read(), socket указывают на файловые дескрипторы Unix; HANDLE и CloseHandle() — на Windows; __get__() и property — на Python.

Источники

При использовании материалов сайта необходимо указывать ссылку на TGLand.ru. Если вы копируете фрагменты текста в интернете, прямая гиперссылка, доступная для индексации поисковыми системами, должна быть размещена в начале материала.