Spec-Zone.ru › Trio

Ввод/вывод в Trio

Абстрактный API потоков

Trio предоставляет набор абстрактных базовых классов, которые определяют стандартный интерфейс для однонаправленных и двунаправленных байтовых потоков.

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

  • trio.SocketStream оборачивает необработанный сокет (например, TCP-соединение по сети) и преобразует его в стандартный интерфейс потока.

  • trio.SSLStream — это «адаптер потока», который может принять любой объект, реализующий интерфейс trio.abc.Stream, и преобразовать его в зашифрованный поток. В Trio стандартный способ общения по SSL через сеть — это обернуть SSLStream вокруг SocketStream.

  • Если вы запускаете подпроцесс, вы можете получить SendStream, который позволяет записывать в его стандартный ввод, и ReceiveStream, который позволяет читать из его стандартного вывода. Если по какой-то причине вам нужно было использовать SSL для взаимодействия с подпроцессом, вы могли бы использовать StapledStream, чтобы объединить его стандартный ввод/вывод в один двунаправленный Stream, а затем обернуть это в SSLStream:

    ssl_context = ssl.create_default_context()
    ssl_context.check_hostname = False
    s = SSLStream(StapledStream(process.stdin, process.stdout), ssl_context)
  • Иногда вам нужно подключиться к серверу HTTPS, но вам нужно пройти через веб-прокси… и прокси также использует HTTPS. Поэтому вам приходится делать SSL поверх SSL. В Trio это тривиально — просто оберните ваш первый SSLStream во второй SSLStream:

    # Get a raw SocketStream connection to the proxy:
    s0 = await open_tcp_stream("proxy", 443)
    
    # Set up SSL connection to proxy:
    s1 = SSLStream(s0, proxy_ssl_context, server_hostname="proxy")
    # Request a connection to the website
    await s1.send_all(b"CONNECT website:443 / HTTP/1.0\r\n\r\n")
    await check_CONNECT_response(s1)
    
    # Set up SSL connection to the real website. Notice that s1 is
    # already an SSLStream object, and here we're wrapping a second
    # SSLStream object around it.
    s2 = SSLStream(s1, website_ssl_context, server_hostname="website")
    # Make our request
    await s2.send_all(b"GET /index.html HTTP/1.0\r\n\r\n")
    ...
  • Модуль trio.testing предоставляет набор гибких реализаций объектов потоков в памяти, поэтому если у вас есть реализация протокола для тестирования, вы можете запустить две задачи, настроить виртуальный «сокет», соединяющий их, и затем делать такие вещи, как вводить случайные, но повторяющиеся задержки в соединение.

Абстрактные базовые классы

Обзор: абстрактные базовые классы для ввода/вывода

Абстрактный базовый класс

Наследуется от…

Добавляет эти абстрактные методы…

И эти конкретные методы.

Примеры реализаций

AsyncResource

aclose()

__aenter__, __aexit__

Асинхронные объекты файлов

SendStream

AsyncResource

send_all(), wait_send_all_might_not_block()

MemorySendStream

ReceiveStream

AsyncResource

receive_some()

__aiter__, __anext__

MemoryReceiveStream

Stream

SendStream, ReceiveStream

SSLStream

HalfCloseableStream

Stream

send_eof()

SocketStream, StapledStream

Listener

AsyncResource

accept()

SocketListener, SSLListener

SendChannel

AsyncResource

send()

MemorySendChannel

ReceiveChannel

AsyncResource

receive()

__aiter__, __anext__

MemoryReceiveChannel

Channel

SendChannel, ReceiveChannel

class trio.abc.AsyncResource

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

Этот класс различает «вежливые» закрытия, которые могут выполнять ввод/вывод и, следовательно, блокироваться, и «принудительное» закрытие, которое не может. Например, чистая остановка TLS-шифрованного соединения требует отправки сообщения «до свидания»; но если узел стал нереагирующим, то отправка этого сообщения может блокироваться вечно, поэтому мы можем просто прервать соединение. Поэтому метод aclose() необычен тем, что он всегда должен закрывать соединение (или, по крайней мере, сделать все возможное) даже если произойдет ошибка; ошибка указывает на неудачу достижения вежливости, а не на неудачу закрытия соединения.

Объекты, реализующие этот интерфейс, могут использоваться в качестве асинхронных контекстных менеджеров, т.е. вы можете написать:

async with create_resource() as some_async_resource:
    ...

Вход в контекстный менеджер является синхронным (не контрольная точка); при выходе из него вызывается метод aclose(). Стандартные реализации __aenter__ и __aexit__ должны быть достаточными для всех подклассов.

abstractmethod await aclose() → None

Закрыть этот ресурс, возможно, заблокировав.

ВАЖНО: Этот метод может заблокироваться, чтобы выполнить «вежливое» завершение. Но если это не удастся, то он все равно обязан закрыть все базовые ресурсы перед возвратом. Ошибка из этого метода указывает на неудачу достижения вежливости, а не на неудачу закрытия соединения.

Например, предположим, что мы вызываем aclose() для TLS-шифрованного соединения. Это требует отправки сообщения «до свидания»; но если узел стал нереагирующим, то наша попытка отправить это сообщение может заблокироваться навсегда, и в конечном итоге истечь и быть отменена. В этом случае метод aclose() в SSLStream немедленно закроет базовый поток транспорта с помощью trio.aclose_forcefully() перед поднятием Cancelled.

Если ресурс уже закрыт, то этот метод должен успешно завершиться без ошибок.

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

См. также: trio.aclose_forcefully().

await trio.aclose_forcefully(resource: AsyncResource) → None

Закрыть асинхронный ресурс или асинхронный генератор немедленно, без блокировки для выполнения плавного завершения.

AsyncResource объекты гарантируют, что если их метод aclose() прерван, то они всё равно закроют ресурс (хотя и, возможно, не плавно). aclose_forcefully() — это удобная функция, которая использует это поведение, чтобы позволить вам принудительно закрыть ресурс без блокировки: она работает, вызывая await resource.aclose() и затем немедленно прерывая его.

Большинству пользователей это не потребуется, но это может быть полезно на путях очистки, где вы не можете позволить себе блокировку, или если вы хотите закрыть ресурс и не беспокоитесь о плавном завершении. Например, если SSLStream столкнётся с ошибкой и не сможет выполнить своё плавное завершение, то нет смысла ждать плавного закрытия базового транспорта, поэтому он вызывает await aclose_forcefully(self.transport_stream).

Обратите внимание, что эта функция асинхронная и работает как контрольная точка, но, в отличие от большинства асинхронных функций, она не может блокироваться неопределённо долго (по крайней мере, предполагая, что базовый объект ресурса реализован правильно).

class trio.abc.SendStream

Bases: AsyncResource

Стандартный интерфейс для отправки данных по потоку байтов.

Базовый поток может быть однонаправленным или двунаправленным. Если он двунаправленный, то, вероятно, вы также хотите реализовать ReceiveStream, что делает ваш объект Stream.

SendStream объекты также реализуют интерфейс AsyncResource, поэтому их можно закрыть, вызвав aclose() или используя блок async with.

Если вы хотите отправлять объекты Python, а не сырые байты, см. SendChannel.

abstractmethod await send_all(data: bytes | bytearray | memoryview) → None

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

Параметры:

data (bytes, bytearray, или memoryview) – Данные для отправки.

Исключения:

  • trio.BusyResourceError – если другая задача уже выполняет send_all(), wait_send_all_might_not_block() или HalfCloseableStream.send_eof() на этом потоке.

  • trio.BrokenResourceError – если что-то пошло не так, и поток сломан.

  • trio.ClosedResourceError – если вы ранее закрыли этот объект потока, или если другая задача закрывает этот объект потока во время выполнения send_all().

Большинство операций низкого уровня в Trio обеспечивают гарантию: если они возбуждают trio.Cancelled, это означает, что они не произвели никакого эффекта, поэтому система остаётся в известном состоянии. Это неверно для send_all(). Если эта операция вызывает trio.Cancelled (или любую другую ошибку), то она может отправить некоторые, все или ни одного из запрошенных данных, и нет способа узнать, какие именно.

abstractmethod await wait_send_all_might_not_block() → None

Блокировать, пока не станет возможным, что send_all() может не заблокироваться.

Этот метод может вернуть раньше: возможно, что после возврата send_all() всё ещё будет блокироваться. (В худшем случае, если нет лучшей реализации, то он может всегда возвращаться немедленно без блокировки. Однако приятно делать лучше, когда это возможно.)

Этот метод не должен возвращаться поздно: если возможно, что send_all() завершится без блокировки, то он должен вернуть значение. При реализации следует ориентироваться на раннее возвращение.

Исключения:

  • trio.BusyResourceError – если другая задача уже выполняет send_all(), wait_send_all_might_not_block() или HalfCloseableStream.send_eof() на этом потоке.

  • trio.BrokenResourceError – если что-то пошло не так, и поток сломан.

  • trio.ClosedResourceError – если вы ранее закрыли этот объект потока, или если другая задача закрывает этот объект потока во время выполнения wait_send_all_might_not_block().

Примечание

Этот метод предназначен для помощи в реализации протоколов, которые хотят отложить выбор отправляемых данных до последнего момента. Например, предположим, что вы работаете над реализацией сервера удалённой графики, такого как VNC, и соединение по сети в настоящее время перегружено, так что если вы вызовете send_all() сейчас, то он будет ожидать 0,5 секунды, прежде чем фактически что-то отправлять. В этом случае нет смысла делать снимок экрана, затем ждать 0,5 секунды, а затем отправлять его, потому что экран будет меняться, пока вы ждёте; лучше подождать 0,5 секунды, затем сделать снимок экрана и отправить его, так как таким образом отправляемые данные будут более актуальными. Использование wait_send_all_might_not_block() позволяет реализовать лучшую стратегию.

Если вы используете этот метод, возможно, вам также стоит почитать TCP_NOTSENT_LOWAT.

Дополнительное чтение:

  • Приоритетизация работает только тогда, когда есть ожидающие данные для приоритезации

  • WWDC 2015: Ваше приложение и сети следующего поколения: слайды, видео и транскрипт

class trio.abc.ReceiveStream

Bases: AsyncResource

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

Основной поток может быть однонаправленным или двунаправленным. Если он двунаправленный, вероятно, вам также потребуется реализовать SendStream, что сделает ваш объект Stream.

ReceiveStream объекты также реализуют интерфейс AsyncResource, поэтому их можно закрыть, вызвав aclose() или используя блок async with.

Если вы хотите получать объекты Python, а не сырые байты, см. ReceiveChannel.

ReceiveStream объекты могут использоваться в циклах async for. Каждый итерация вернёт кусок байтов произвольного размера, как если бы вы вызвали receive_some без аргументов. Каждый кусок будет содержать как минимум один байт, а цикл автоматически завершится при достижении конца файла.

abstractmethod await receive_some(max_bytes: int | None = None) → bytes | bytearray

Ожидает появления данных в потоке и возвращает часть из них.

Возвращаемое значение b"" (пустая строка байтов) указывает на достижение конца файла. Реализации должны гарантировать возврат b"" только в том случае, если поток достиг конца файла!

Параметры:

max_bytes (int) – Максимальное количество байтов для возврата. Должно быть больше нуля. Необязательно; если опущено, объект потока может выбрать разумное значение по умолчанию.

Возвращаемое значение:

Полученные данные.

Тип возвращаемого значения:

bytes или bytearray

Исключения:

  • trio.BusyResourceError – если две задачи пытаются вызвать receive_some() на одном потоке одновременно.

  • trio.BrokenResourceError – если что-то пошло не так и поток повреждён.

  • trio.ClosedResourceError – если вы ранее закрыли этот объект потока или другая задача закрыла этот объект потока во время выполнения receive_some().

class trio.abc.Stream

Bases: SendStream, ReceiveStream

Стандартный интерфейс для взаимодействия с двунаправленными потоками байтов.

Объект Stream реализует как интерфейс SendStream, так и ReceiveStream.

При реализации этого интерфейса стоит задуматься, возможно ли реализовать HalfCloseableStream.

class trio.abc.HalfCloseableStream

Базы: Stream

Этот интерфейс расширяет Stream, позволяя также закрывать часть отправки потока без закрытия части получения.

abstractmethod await send_eof() → None

Отправить индикатор конца файла в этом потоке, если это возможно.

Различие между send_eof() и aclose() заключается в том, что send_eof() — это однонаправленный индикатор конца файла. После вызова этого метода не следует пытаться отправлять больше данных по этому потоку, и ваш удалённый партнёр должен получить индикатор конца файла (в конечном итоге, после получения всех данных, отправленных вами до этого). Однако они могут продолжать отправлять вам данные, и вы можете продолжить их получать, вызывая receive_some(). Можно представить это как вызов aclose() только для части SendStream объекта потока (и, на самом деле, именно так trio.StapledStream это реализует).

Примеры:

  • В сокете это соответствует shutdown(..., SHUT_WR) (страница man).

  • Протокол SSH предоставляет возможность мультиплексирования двунаправленных «каналов» поверх одного зашифрованного соединения. Реализация SSH с Trio может экспонировать эти каналы как объекты HalfCloseableStream, а вызов send_eof() отправит запрос SSH_MSG_CHANNEL_EOF (см. RFC 4254 §5.3).

  • В SSL/TLS-зашифрованном соединении протокол не предоставляет никакого способа выполнить однонаправленное закрытие без полного закрытия соединения, поэтому SSLStream реализует Stream, а не HalfCloseableStream.

Если EOF уже отправлен, то этот метод должен успешно завершиться без действий.

Исключения:

  • trio.BusyResourceError — если другая задача уже выполняет send_all(), wait_send_all_might_not_block() или send_eof() в этом потоке.

  • trio.BrokenResourceError — если что-то пошло не так и поток сломан.

  • trio.ClosedResourceError — если вы ранее закрыли этот объект потока или другая задача закрыла этот объект потока во время выполнения send_eof().

class trio.abc.Listener

Базы: AsyncResource, Generic[T_resource]

Стандартный интерфейс для прослушивания входящих подключений.

Объекты Listener также реализуют интерфейс AsyncResource, поэтому их можно закрыть, вызвав aclose() или с помощью блока async with.

abstractmethod await accept() → T_resource

Подождите, пока не придёт входящее соединение, и верните его.

Возвращает:

Объект, представляющий входящее соединение. На практике это обычно какой-то Stream, но в принципе можно определить Listener, который возвращает, например, объекты каналов.

Тип возвращаемого значения:

AsyncResource

Исключения:

  • trio.BusyResourceError — если две задачи пытаются вызвать accept() на одном прослушивателе одновременно.

  • trio.ClosedResourceError — если вы ранее закрыли этот объект прослушивателя или другая задача закрыла этот объект прослушивателя во время выполнения accept().

Прослушиватели обычно не поднимают BrokenResourceError, потому что для прослушивателей нет общего условия «сеть/удаленный партнёр разорвал соединение», которое можно обработать универсальным способом, как это есть для потоков. Другие ошибки могут возникнуть и быть подняты из accept() — например, если у вас закончились дескрипторы файлов, то вы можете получить OSError с установленным errno на EMFILE.

class trio.abc.SendChannel

Bases: AsyncResource, Generic[SendType]

Стандартный интерфейс для отправки объектов Python получателю.

SendChannel объекты также реализуют интерфейс AsyncResource, поэтому их можно закрыть, вызвав aclose или используя блок async with.

Если вы хотите отправить сырые байты, а не объекты Python, см. SendStream.

abstractmethod await send(value: SendType) → None

Попытка отправить объект по каналу, блокируя, если необходимо.

Параметры:

значение (объект) – Объект для отправки.

Исключения:

  • trio.BrokenResourceError – если что-то пошло не так, и канал сломан. Например, вы можете получить это, если получатель уже закрыт.

  • trio.ClosedResourceError – если вы ранее закрыли этот SendChannel объект или если другой процесс закрывает его во время выполнения send().

  • trio.BusyResourceError – некоторые каналы позволяют нескольким процессам вызывать send одновременно, а другие нет. Если вы попытаетесь вызвать send одновременно из нескольких процессов на канале, который этого не поддерживает, то вы можете получить BusyResourceError.

class trio.abc.ReceiveChannel

Bases: AsyncResource, Generic[ReceiveType]

Стандартный интерфейс для получения объектов Python от отправителя.

Вы можете перебирать ReceiveChannel с помощью цикла async for:

async for value in receive_channel:
    ...

Это эквивалентно многократному вызову receive(). Цикл завершается без ошибки, когда receive вызывает EndOfChannel.

ReceiveChannel объекты также реализуют интерфейс AsyncResource, поэтому их можно закрыть, вызвав aclose или используя блок async with.

Если вы хотите получить сырые байты, а не объекты Python, см. ReceiveStream.

abstractmethod await receive() → ReceiveType

Попытка получить входящий объект, блокируя, если необходимо.

Возвращает:

Полученный объект.

Тип возвращаемого значения:

объект

Исключения:

  • trio.EndOfChannel – если отправитель был закрыт корректно, и больше объектов не поступает. Это не состояние ошибки.

  • trio.ClosedResourceError – если вы ранее закрыли этот ReceiveChannel объект.

  • trio.BrokenResourceError – если что-то пошло не так, и канал сломан.

  • trio.BusyResourceError – некоторые каналы позволяют нескольким процессам вызывать receive одновременно, а другие нет. Если вы попытаетесь вызвать receive одновременно из нескольких процессов на канале, который этого не поддерживает, то вы можете получить BusyResourceError.

class trio.abc.Channel

Bases: SendChannel[T], ReceiveChannel[T]

Стандартный интерфейс для взаимодействия с двунаправленными каналами.

Channel это объект, который реализует как интерфейс SendChannel, так и ReceiveChannel, поэтому вы можете как отправлять, так и получать объекты.

Общие инструменты для потоков

В настоящее время Trio предоставляет общий помощник для написания серверов, которые слушают подключения с использованием одного или нескольких Listener, и общий служебный класс для работы с потоками. И если вы хотите протестировать код, написанный с учетом интерфейса потоков, вы также можете ознакомиться с Потоками в trio.testing.

await trio.serve_listeners(handler: Callable[[StreamT], Awaitable[object]], listeners: list[ListenerT], *, handler_nursery: Nursery | None = None, task_status: TaskStatus[list[ListenerT]] = TASK_STATUS_IGNORED) → NoReturn

Слушать входящие подключения на listeners, и для каждого из них запускать задачу, выполняющую handler(stream).

Предупреждение

Если handler вызывает исключение, то эта функция ничего не делает для его перехвата — поэтому по умолчанию исключение будет распространяться и приведет к сбою вашего сервера. Если вы этого не хотите, перехватите исключения внутри вашего handler или используйте объект handler_nursery, который обрабатывает исключения другим способом.

Параметры:

  • handler – Асинхронная вызываемая функция, которая будет вызываться как handler_nursery.start_soon(handler, stream) для каждого входящего подключения.

  • listeners – Список объектов Listener. serve_listeners() несет ответственность за их закрытие.

  • handler_nursery – Nursery, используемый для запуска обработчиков, или любой объект с методом start_soon. Если None (по умолчанию), то serve_listeners() создаст новую nursery внутри и будет использовать её.

  • task_status – Эта функция может использоваться с nursery.start, которая вернёт listeners.

Возвращаемое значение:

Эта функция никогда не возвращает значение, пока не отменена.

Обработка ресурсов:

Если handler не закрывает stream, то он будет закрыт с помощью trio.aclose_forcefully().

Обработка ошибок:

Большинство ошибок, поступающих из accept(), допускается распространять (приводя к сбою сервера). Однако некоторые ошибки — те, которые указывают на временную перегрузку сервера — обрабатываются специально. Это OSError с одним из следующих errno:

  • EMFILE: у процесса закончились дескрипторы файлов

  • ENFILE: у системы закончились дескрипторы файлов

  • ENOBUFS, ENOMEM: ядро столкнулось с какой-то проблемой ограничений памяти при попытке создать объект сокета

Когда serve_listeners() получает одну из этих ошибок, то:

  • Записывает ошибку в стандартный логгер trio.serve_listeners (уровень = ERROR, с информацией об исключении). По умолчанию это приводит к её выводу в stderr.

  • Ждет 100 мс перед повторным вызовом accept, в надежде на восстановление системы.

class trio.StapledStream(send_stream: SendStreamT, receive_stream: ReceiveStreamT)

Bases: HalfCloseableStream, Generic[SendStreamT, ReceiveStreamT]

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

Параметры:

  • send_stream (SendStream) – Поток для отправки данных.

  • receive_stream (ReceiveStream) – Поток для получения данных.

Пример

Глупый способ создать поток, который эхом возвращает всё, что вы в него вводите:

left, right = trio.testing.memory_stream_pair()
echo_stream = StapledStream(SocketStream(left), SocketStream(right))
await echo_stream.send_all(b"x")
assert await echo_stream.receive_some() == b"x"

StapledStream объекты реализуют методы интерфейса HalfCloseableStream. У них также есть два дополнительных общедоступных атрибута:

send_stream

Подлежащий SendStream. send_all() и wait_send_all_might_not_block() делегированы этому объекту.

receive_stream

Подлежащий ReceiveStream. receive_some() делегированы этому объекту.

await aclose() → None

Вызывает aclose для обоих подлежащих потоков.

await receive_some(max_bytes: int | None = None) → bytes

Вызывает self.receive_stream.receive_some.

await send_all(data: bytes | bytearray | memoryview) → None

Вызывает self.send_stream.send_all.

await send_eof() → None

Закрывает сторону отправки потока.

Если self.send_stream.send_eof() существует, то этот вызов. Иначе это вызов self.send_stream.aclose().

await wait_send_all_might_not_block() → None

Вызывает self.send_stream.wait_send_all_might_not_block.

Сокеты и сетевое взаимодействие

Интерфейс высокого уровня для работы с сетью построен на основе абстракции потока.

await trio.open_tcp_stream(host: str | bytes, port: int, *, happy_eyeballs_delay: float | None = 0.25, local_address: str | None = None) → SocketStream

Подключение к указанному хосту и порту по протоколу TCP.

Если у заданного host ассоциировано несколько IP-адресов, возникает проблема: какой использовать?

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

Другой подход — попытаться подключиться ко всем адресам одновременно, параллельно, и использовать первое успешное подключение, отказываясь от остальных. Это будет быстро, но создаст излишнюю нагрузку на сеть и удалённый сервер.

Данная функция балансирует между этими двумя крайностями: она обрабатывает доступные адреса по одному, как в первом подходе; но, если прошло happy_eyeballs_delay секунд, и подключение всё ещё ожидается, то она теряет терпение и начинает следующую попытку подключения параллельно. Как только одна попытка подключения окажется успешной, все остальные попытки отменяются. Это позволяет избежать излишней нагрузки, поскольку большинство подключений происходит после одной или двух попыток, но если один из адресов недоступен, это не замедляет нас слишком сильно.

Это известно как алгоритм «счастливых глаз», и наша конкретная реализация моделируется под тем, как Chrome подключается к веб-серверам; см. RFC 6555 для получения дополнительной информации.

Параметры:

  • host (str или bytes) — Хост для подключения. Может быть IPv4-адресом, IPv6-адресом или именем хоста.

  • port (int) — Порт для подключения.

  • happy_eyeballs_delay (float или None) — Количество секунд ожидания каждой попытки подключения для успеха или неудачи, прежде чем начать другую попытку параллельно. Устанавливается в значение None, если нужно ограничиться только одной попыткой подключения (как в socket.create_connection()). Значение по умолчанию: 0.25 (250 мс).

  • local_address (None или str) —

    Локальный IP-адрес или имя хоста, используемый в качестве источника исходящих подключений. Если None, ОС выбирает IP-адрес источника.

    Это полезно в некоторых экзотических сетевых конфигурациях, где у хоста несколько IP-адресов, и требуется принудительно использовать определённый.

    Обратите внимание, что если вы передаёте IPv4 local_address, то не сможете подключиться к IPv6-хостам, и наоборот. Если вы хотите воспользоваться этим для принудительного использования IPv4 или IPv6 без указания точного адреса источника, вы можете использовать IPv4-адрес маскирования local_address="0.0.0.0" или IPv6-адрес маскирования local_address="::".

Возвращаемое значение:

объект Stream, подключенный к указанному серверу.

Тип возвращаемого значения:

SocketStream

Исключения:

OSError — если подключение не удаётся.

См. также

open_ssl_over_tcp_stream

await trio.serve_tcp(handler: Callable[[trio.SocketStream], Awaitable[object]], port: int, *, host: str | bytes | None = None, backlog: int | None = None, handler_nursery: trio.Nursery | None = None, task_status: TaskStatus[list[trio.SocketListener]] = TASK_STATUS_IGNORED) → None

Принимает входящие TCP-соединения и для каждого запускает задачу, выполняющую handler(stream).

Это тонкий оберточный модуль над open_tcp_listeners() и serve_listeners() — см. их для получения подробной информации.

Предупреждение

Если handler вызывает исключение, эта функция ничего не делает, чтобы его перехватить — поэтому по умолчанию исключение будет передано и приведёт к сбою вашего сервера. Если вы этого не хотите, перехватывайте исключения внутри вашей handler или используйте объект handler_nursery, который реагирует на исключения каким-то другим способом.

При использовании с nursery.start вы получаете вновь открытые слушатели. Например, если вы хотите запустить сервер в наборе тестов и затем подключиться к нему, чтобы проверить, что он работает правильно, вы можете использовать что-то вроде:

from trio import SocketListener, SocketStream
from trio.testing import open_stream_to_socket_listener

async with trio.open_nursery() as nursery:
    listeners: list[SocketListener] = await nursery.start(serve_tcp, handler, 0)
    client_stream: SocketStream = await open_stream_to_socket_listener(listeners[0])

    # Then send and receive data on 'client_stream', for example:
    await client_stream.send_all(b"GET / HTTP/1.0\r\n\r\n")

Это предотвращает несколько распространённых проблем:

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

  2. Это ожидает, пока сервер не начнёт принимать подключения на этом порте, прежде чем start вернёт значение, таким образом исключая гонку, при которой входящее соединение поступает до того, как сервер будет готов.

  3. Это использует объект Listener, чтобы узнать выбранный порт, поэтому он может подключиться к правильному месту.

Параметры:

  • handler — Обработчик, который нужно запустить для каждого входящего подключения. Передаётся в serve_listeners().

  • port — Порт для прослушивания. Используйте 0, чтобы ядро выбрало свободный порт. Передаётся в open_tcp_listeners().

  • host (str, bytes или None) — Интерфейс хоста для прослушивания; используйте None для привязки к адресной маске. Передаётся в open_tcp_listeners().

  • backlog — Буфер ожидающих подключений, или None для выбора подходящего по умолчанию. Передаётся в open_tcp_listeners().

  • handler_nursery — Детский сад для запуска обработчиков или None для использования внутреннего. Передаётся в serve_listeners().

  • task_status — Эта функция может быть использована с nursery.start.

Возвращаемое значение:

Эта функция возвращает только при отмене.

await trio.open_ssl_over_tcp_stream(host: str | bytes, port: int, *, https_compatible: bool = False, ssl_context: ssl.SSLContext | None = None, happy_eyeballs_delay: float | None = 0.25) → trio.SSLStream[SocketStream]

Установите зашифрованное соединение TLS с указанным хостом и портом по протоколу TCP.

Это удобная обертка, которая вызывает open_tcp_stream() и оборачивает результат в SSLStream.

Эта функция не выполняет рукопожатие TLS; вы можете сделать это вручную, вызвав do_handshake(), или же оно будет выполнено автоматически при первом отправлении или получении данных.

Параметры:

  • host (bytes или str) – Хост, к которому нужно подключиться. Сервер должен иметь действительный сертификат TLS для этого имени хоста.

  • port (int) – Порт для подключения.

  • https_compatible (bool) – Установите значение True, если вы подключаетесь к веб-серверу. Подробнее см. SSLStream. По умолчанию: False.

  • ssl_context (SSLContext или None) – Контекст SSL для использования. Если None (по умолчанию), будет вызван ssl.create_default_context() для создания контекста.

  • happy_eyeballs_delay (float) – См. open_tcp_stream().

Возвращаемое значение:

зашифрованное соединение с сервером.

Тип возвращаемого значения:

trio.SSLStream

await trio.serve_ssl_over_tcp(handler: Callable[[trio.SSLStream[SocketStream]], Awaitable[object]], port: int, ssl_context: ssl.SSLContext, *, host: str | bytes | None = None, https_compatible: bool = False, backlog: int | None = None, handler_nursery: trio.Nursery | None = None, task_status: trio.TaskStatus[list[trio.SSLListener[SocketStream]]] = TASK_STATUS_IGNORED) → NoReturn

Принимать входящие подключения TCP и для каждого из них запускать задачу, выполняющую handler(stream).

Это тонкая удобная обертка вокруг open_ssl_over_tcp_listeners() и serve_listeners() – см. их для получения подробной информации.

Предупреждение

Если handler вызывает исключение, то эта функция не делает ничего особенного для его перехвата – поэтому по умолчанию исключение будет распространено и завершит работу вашего сервера. Если этого не хотите, перехватывайте исключения внутри handler или используйте объект handler_nursery, который каким-то образом обрабатывает исключения.

При использовании с nursery.start вы получаете вновь открытые слушатели. Смотрите документацию для serve_tcp() для примера, где это полезно.

Параметры:

  • handler – Обработчик, который нужно запустить для каждого входящего подключения. Передаётся в serve_listeners().

  • port (int) – Порт для прослушивания. Используйте 0, чтобы ядро выбрало открытый порт. В конечном итоге передаётся в open_tcp_listeners().

  • ssl_context (SSLContext) – Контекст SSL, который нужно использовать для всех входящих подключений. Передаётся в open_ssl_over_tcp_listeners().

  • host (str, bytes или None) – Адрес для привязки; используйте None для привязки к универсальному адресу. В конечном итоге передаётся в open_tcp_listeners().

  • https_compatible (bool) – Установите значение True, если хотите использовать TLS в стиле «HTTPS». Подробнее см. SSLStream.

  • backlog (int или None) – См. SSLStream для подробностей.

  • handler_nursery – Nursery для запуска обработчиков или None для использования внутренней nursery. Передаётся в serve_listeners().

  • task_status – Эта функция может быть использована с nursery.start.

Возвращаемое значение:

Функция возвращает только при отмене.

await trio.open_unix_socket(filename: str | bytes | PathLike[str] | PathLike[bytes]) → SocketStream

Открывает соединение с указанным сокет Unix-домена.

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

Параметры:

filename (str или bytes) – Имя файла для открытия соединения.

Возвращаемое значение:

Stream, подключённый к указанному файлу.

Тип возвращаемого значения:

SocketStream

Исключения:

  • OSError – Если сокет файла не удалось подключиться.

  • RuntimeError – Если сокеты AF_UNIX не поддерживаются.

Потоковое подключение к сокету

Наследуется от HalfCloseableStream

Реализация интерфейса trio.abc.HalfCloseableStream на основе сырого сетевого сокета.

Параметры:

socket – Объект сокета Trio для обертывания. Должен быть типа SOCK_STREAM и подключен.

По умолчанию для TCP-сокетов SocketStream включает TCP_NODELAY и (на платформах, где это поддерживается) включает TCP_NOTSENT_LOWAT с разумным размером буфера (в настоящее время 16 КБ) – см. вопрос #72 для обсуждения. Конечно, вы можете переопределить эти значения по умолчанию, вызвав setsockopt().

После создания объекта SocketStream он реализует весь интерфейс trio.abc.HalfCloseableStream. Кроме того, он предоставляет несколько дополнительных функций:

socket

Объект сокета Trio, который оборачивает это потоковое соединение.

await aclose() → None

getsockopt(level: int, option: int, buffersize: int = 0) → int | bytes

Проверяет текущее значение параметра для сокета.

См. socket.socket.getsockopt() для деталей.

await receive_some(max_bytes: int | None = None) → bytes

await send_all(data: bytes | bytearray | memoryview) → None

await send_eof() → None

setsockopt(level: int, option: int, value: int | Buffer | None, length: int | None = None) → None

Устанавливает параметр для сокета.

См. socket.socket.setsockopt() для деталей.

await wait_send_all_might_not_block() → None

class trio.SocketListener(socket: SocketType)

Наследуется от Listener[SocketStream]

Слушатель Listener использует сокет для прослушивания входящих подключений в виде объектов SocketStream.

Параметры:

socket – Объект сокета Trio для обертывания. Должен быть типа SOCK_STREAM и прослушивать.

Обратите внимание, что SocketListener «принимает на себя ответственность» за предоставленный сокет; закрытие SocketListener также закроет сокет.

socket

Объект сокета Trio, который оборачивает это потоковое соединение.

await accept() → SocketStream

Принимает входящее подключение.

Возвращает:

SocketStream

Исключения:

  • OSError – если базовый вызов accept вызывает неожиданную ошибку.

  • ClosedResourceError – если вы уже закрыли сокет.

Этот метод обрабатывает обычные ошибки, такие как ECONNABORTED, но передает другие ошибки своему вызывающему объекту. В частности, он не делает никаких особых усилий для обработки ошибок истощения ресурсов, таких как EMFILE, ENFILE, ENOBUFS, ENOMEM.

await aclose() → None

Закрыть этот слушатель и его базовый сокет.

await trio.open_tcp_listeners(port: int, *, host: str | bytes | None = None, backlog: int | None = None) → list[SocketListener]

Создаёт объекты SocketListener для прослушивания TCP-подключений.

Параметры:

  • port (int) –

    Порт для прослушивания.

    Если вы используете 0 в качестве порта, ядро автоматически выберет произвольный свободный порт. Но будьте осторожны: если вы используете эту функцию при привязке к нескольким IP-адресам, то каждый IP-адрес получит свой случайный порт, и возвращённые слушатели, вероятно, будут прослушивать на разных портах. В частности, это произойдёт, если вы используете host=None (что является значением по умолчанию), потому что в этом случае open_tcp_listeners() будет привязываться как к IPv4-маркеру (0.0.0.0), так и к IPv6-маркеру (::).

  • host (str, bytes, or None) –

    Локальный интерфейс для привязки. Передаётся в getaddrinfo() с установленным флагом AI_PASSIVE.

    Если вы хотите привязаться к маркеру адреса для IPv4 и IPv6, чтобы принять подключения на всех доступных интерфейсах, передайте None. Это значение по умолчанию.

    Если вам нужен конкретный интерфейс, передайте его IP-адрес или имя хоста. Если имя хоста разрешается до нескольких IP-адресов, эта функция откроет по одному слушателю на каждом из них.

    Если вы хотите использовать только IPv4 или только IPv6, но хотите принимать подключения на всех интерфейсах, передайте семейно-специфический маркер адреса: "0.0.0.0" для только IPv4 и "::" для только IPv6.

  • backlog (int or None) – Размер очереди ожидающих соединений. Если оставить None, Trio выберет хорошее значение по умолчанию. (В настоящее время: значение, настроенное вашей системой как максимальный размер очереди.)

Возвращает:

Список объектов SocketListener

Исключения:

TypeError –

await trio.open_ssl_over_tcp_listeners(port: int, ssl_context: ssl.SSLContext, *, host: str | bytes | None = None, https_compatible: bool = False, backlog: int | None = None) → list[trio.SSLListener[SocketStream]]

Начинает прослушивание SSL/TLS-шифрованных TCP-соединений на заданном порту.

Параметры:

  • port (int) – Порт для прослушивания. См. open_tcp_listeners().

  • ssl_context (SSLContext) – Контекст SSL, который будет использоваться для всех входящих соединений.

  • host (str, bytes, или None) – Адрес для привязки; используйте None для привязки к адресу подстановки. См. open_tcp_listeners().

  • https_compatible (bool) – Подробности см. в SSLStream.

  • backlog (int или None) – Подробности см. в open_tcp_listeners().

Поддержка SSL/TLS

Trio предоставляет поддержку SSL/TLS, основанную на стандартном модуле ssl. Классы Trio SSLStream и SSLListener получают свою конфигурацию из ssl.SSLContext, который можно создать с помощью ssl.create_default_context() и настроить с помощью других констант и функций в модуле ssl.

Предупреждение

Избегайте непосредственного создания объекта ssl.SSLContext. У только что созданного SSLContext более слабые настройки безопасности, чем у объекта, возвращаемого функцией ssl.create_default_context().

Вместо использования ssl.SSLContext.wrap_socket(), создайте объект SSLStream:

class trio.SSLStream(transport_stream: T_Stream, ssl_context: SSLContext, *, server_hostname: str | bytes | None = None, server_side: bool = False, https_compatible: bool = False)

Bases: Stream, Generic[T_Stream]

Шифрованное общение с использованием SSL/TLS.

SSLStream оборачивает произвольный Stream и позволяет вам выполнять шифрованное общение по нему, используя обычный интерфейс Stream. Вы передаёте обычные данные в send_all(), затем он шифрует их и отправляет зашифрованные данные по базовому Stream; receive_some() извлекает зашифрованные данные из базового Stream и расшифровывает их перед возвратом.

Перед использованием этого класса внимательно ознакомьтесь с документацией стандартной библиотеки ssl, а также, вероятно, с другой общей документацией по SSL/TLS. SSL/TLS тонкий и быстро сердится. Серьёзно. Я не шучу.

Параметры:

  • transport_stream (Поток) – Поток, используемый для передачи зашифрованных данных. Требуется.

  • ssl_context (SSLContext) – SSLContext, используемый для этого соединения. Требуется. Обычно создаётся вызовом ssl.create_default_context().

  • server_hostname (str, bytes, или None) – Имя сервера, с которым производится подключение. Используется для SNI и для проверки сертификата сервера (если проверка по имени включена). Это фактически обязательно для клиентов и, на самом деле, обязательно, если ssl_context.check_hostname является True.

  • server_side (bool) – Является ли этот поток клиентом или сервером. По умолчанию False, то есть режим клиента.

  • https_compatible (bool) –

    Существуют две версии SSL/TLS, которые часто встречаются в дикой природе: стандартная версия и версия, используемая для HTTPS (HTTP-over-SSL/TLS).

    Стандартные реализации SSL/TLS всегда отправляют криптографически подписанное close_notify сообщение перед закрытием соединения. Это важно, потому что если базовый транспорт просто закроется, то другой стороне не будет известно, было ли соединение намеренно закрыто партнёром, с которым они договорились о криптографическом соединении, или каким-то злоумышленником-«человеком посередине», который не может манипулировать криптографическим потоком, но может манипулировать транспортным уровнем (так называемая «атака обрыва»).

    Однако эта часть стандарта широко игнорируется реальными реализациями HTTPS, что означает, что если вы хотите с ними взаимодействовать, то вам тоже нужно это игнорировать.

    К счастью, это не так плохо, как звучит, потому что протокол HTTP уже включает собственный эквивалент close_notify, поэтому повторное выполнение этого на уровне SSL/TLS избыточно. Но не все протоколы это делают! Поэтому по умолчанию Trio реализует более безопасную стандартную версию (https_compatible=False). Но если вы общаетесь по протоколу HTTPS или по другому протоколу, где close_notify часто пропускаются, то вы должны установить https_compatible=True; с этой установкой Trio не будет ожидать и не будет отправлять close_notify сообщения.

    Если у вас есть код, который был написан для использования ssl.SSLSocket, а теперь вы переносите его в Trio, то вам может быть полезно знать, что различие между SSLStream и ssl.SSLSocket состоит в том, что SSLSocket реализует поведение https_compatible=True по умолчанию.

transport_stream

Базовый транспортный поток, который был передан в __init__. Примером, когда это может быть полезно, является использование SSLStream через SocketStream и вызов метода SocketStream’s setsockopt().

Тип:

trio.abc.Stream

Внутренне этот класс реализован с помощью экземпляра ssl.SSLObject, и все методы и атрибуты SSLObject повторно экспортируются как методы и атрибуты в этом классе. Однако есть одно различие: SSLObject имеет несколько методов, которые возвращают информацию о зашифрованном соединении, такие как cipher() или selected_alpn_protocol(). Если вы вызываете их до установления рукопожатия, когда они не могут вернуть полезные данные, то ssl.SSLObject возвращает None, но trio.SSLStream вызывает NeedHandshakeError.

Это также означает, что если вы регистрируете обратный вызов SNI с помощью sni_callback, то первый аргумент, который получает ваш обратный вызов, будет ssl.SSLObject.

await aclose() → None

Вежливо завершить это соединение и закрыть базовый транспорт.

Если https_compatible равно False (по умолчанию), то это пытается сначала отправить close_notify, а затем закрыть базовый поток, вызвав его метод aclose().

Если https_compatible установлено в True, то это просто закрывает базовый поток и отмечает этот поток как закрытый.

await do_handshake() → None

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

Протокол SSL требует начального обмена рукопожатиями для обмена сертификатами, выбора криптографических ключей и т. д., прежде чем можно будет отправлять или получать какие-либо данные. Вам не нужно вызывать этот метод; если вы этого не сделаете, то SSLStream автоматически выполнит обмен рукопожатиями по мере необходимости, в первый раз, когда вы попытаетесь отправить или получить данные. Но если вы хотите запустить его вручную — например, потому что хотите посмотреть сертификат удаленного узла, прежде чем начать с ним общаться — тогда вы можете вызвать этот метод.

Если начальный обмен рукопожатиями уже выполняется в другой задаче, это ожидает его завершения, а затем возвращается.

Если начальный обмен рукопожатиями уже завершен, это возвращается немедленно, ничего не делая (кроме выполнения контрольной точки).

Предупреждение

Если этот метод отменён, то он может оставить SSLStream в непригодном для использования состоянии. Если это произойдёт, то любая последующая попытка использовать объект вызовет trio.BrokenResourceError.

await receive_some(max_bytes: int | None = None) → bytes | bytearray

Прочтите некоторые данные из базового транспорта, расшифруйте их и верните.

См. trio.abc.ReceiveStream.receive_some() для получения подробной информации.

Предупреждение

Если этот метод отменён во время выполнения начального обмена рукопожатиями или повторного согласования, то он может оставить SSLStream в непригодном для использования состоянии. Если это произойдёт, то любая последующая попытка использования объекта вызовет trio.BrokenResourceError.

await send_all(data: bytes | bytearray | memoryview) → None

Зашифруйте некоторые данные, а затем отправьте их по базовому транспорту.

См. trio.abc.SendStream.send_all() для получения подробной информации.

Предупреждение

Если этот метод отменён, то он может оставить SSLStream в непригодном для использования состоянии. Если это произойдёт, любая попытка использовать объект вызовет trio.BrokenResourceError.

await unwrap() → tuple[Stream, bytes | bytearray]

Чисто закрыть уровень шифрования SSL/TLS, позволив базовому потоку использоваться для нешифрованного обмена.

Вам, скорее всего, это не нужно.

Возвращает:

Пару (transport_stream, trailing_bytes), где transport_stream — это базовый поток транспорта, а trailing_bytes — строка байтов. Поскольку SSLStream не обязательно знает, где будет конец зашифрованных данных, может случиться, что он случайно прочтёт слишком много из базового потока. trailing_bytes содержит эти дополнительные данные; вы должны обработать их так, как будто они были возвращены из вызова transport_stream.receive_some(...).

await wait_send_all_might_not_block() → None

См. trio.abc.SendStream.wait_send_all_might_not_block().

И если вы реализуете сервер, вы можете использовать SSLListener:

class trio.SSLListener(transport_listener: Listener[T_Stream], ssl_context: SSLContext, *, https_compatible: bool = False)

Базируется на Listener[SSLStream[T_Stream]]

A Listener для SSL/TLS-зашифрованных серверов.

SSLListener обертывает другой Listener и преобразует все входящие соединения в зашифрованные соединения, обертывая их в SSLStream.

Параметры:

  • transport_listener (Listener) — Слушатель, входящие соединения которого будут обернуты в SSLStream.

  • ssl_context (SSLContext) — SSLContext, который будет использоваться для входящих соединений.

  • https_compatible (bool) — Передаётся в SSLStream.

transport_listener

Базовый слушатель, который был передан в __init__.

Тип:

trio.abc.Listener

await accept() → SSLStream[T_Stream]

Принимает следующее соединение и оборачивает его в SSLStream.

См. trio.abc.Listener.accept() для получения подробной информации.

await aclose() → None

Закрыть слушателя транспорта.

Некоторые методы SSLStream поднимают NeedHandshakeError, если вы их вызываете до завершения обмена рукопожатиями:

exception trio.NeedHandshakeError

Некоторые методы SSLStream не могут вернуть осмысленные данные до завершения обмена рукопожатиями. Если вы их вызываете до обмена рукопожатиями, они поднимают эту ошибку.

Поддержка DTLS

В Trio также поддерживается Datagram TLS (DTLS), который похож на TLS, но предназначен для ненадежных UDP-соединений. Это может быть полезно для приложений, где надёжная доставка TCP в определённом порядке проблематична, например, для видеоконференций, игр с низкой задержкой и VPN.

В настоящее время для использования DTLS с Trio требуется PyOpenSSL. В будущем мы надеемся также разрешить использование модуля stdlib ssl, но к сожалению, это пока невозможно.

Предупреждение

Обратите внимание, что PyOpenSSL в значительной степени является более низкоуровневым модулем, чем ssl. В частности, в настоящее время он НЕ ИМЕЕТ ВСТРОЕННОГО МЕХАНИЗМА ПРОВЕРКИ СЕРТИФИКАТОВ. Мы настоятельно рекомендуем использовать библиотеку service-identity для проверки имен хостов и сертификатов.

class trio.DTLSEndpoint(socket: SocketType, *, incoming_packets_buffer: int = 10)

Конечная точка DTLS.

Один UDP-сокет может обрабатывать произвольное количество одновременных DTLS-соединений, выступая в качестве клиента или сервера по мере необходимости. Объект DTLSEndpoint содержит UDP-сокет и управляет этими соединениями, которые представлены объектами DTLSChannel.

Параметры:

  • socket – (trio.socket.SocketType): Сокет SOCK_DGRAM. Если вы хотите принимать входящие соединения в режиме сервера, то, вероятно, вам нужно привязать сокет к известному порту.

  • incoming_packets_buffer (int) – Каждый DTLSChannel, использующий этот сокет, имеет свой собственный буфер, который хранит входящие пакеты до тех пор, пока вы не вызовете receive для их чтения. Это позволяет вам изменять размер этого буфера. statistics позволяет проверить, переполнен ли буфер.

socket

incoming_packets_buffer

Оба аргумента конструктора также доступны как атрибуты, на случай если вам нужно получить к ним доступ позже.

connect(address: tuple[str, int], ssl_context: OpenSSL.SSL.Context) → DTLSChannel

Инициализация исходящего DTLS-соединения.

Обратите внимание, что этот метод синхронный. Это потому, что он не фактически не инициирует никакого ввода/вывода — он просто создаёт объект DTLSChannel. Фактический обмен рукопожатием не происходит до тех пор, пока вы не начнёте использовать DTLSChannel. Это даёт вам возможность предварительно выполнить дополнительные настройки, такие как установка MTU и т. д.

Параметры:

  • address – Адрес для подключения. Обычно кортеж (хост, порт), например, ("127.0.0.1", 12345).

  • ssl_context (OpenSSL.SSL.Context) – Объект контекста PyOpenSSL для этого соединения.

Возвращает:

DTLSChannel

await serve(ssl_context: OpenSSL.SSL.Context, async_fn: Callable[[DTLSChannel, Unpack[PosArgsT]], Awaitable[object]], *args: Unpack[PosArgsT], task_status: trio.TaskStatus[None] = TASK_STATUS_IGNORED) → None

Прослушивает входящие соединения и порождает обработчик для каждого из них с помощью внутренней ячейки.

Аналогично serve_tcp, эта функция никогда не возвращается, пока не отменена, или DTLSEndpoint не закрыта и все обработчики не завершат работу.

Использование обычно выглядит так:

async def handler(dtls_channel):
    ...

async with trio.open_nursery() as nursery:
    await nursery.start(dtls_endpoint.serve, ssl_context, handler)
    # ... do other things here ...

Объект dtls_channel, переданный в функцию обработчика, уже выполнил часть обмена «куки» DTLS-процедуры рукопожатия, поэтому адрес отправителя надёжен. Однако фактический криптографический обмен рукопожатием не происходит до тех пор, пока вы не начнёте его использовать, что даёт вам возможность выполнить любые последние настройки и возможность перехватить и обработать ошибки рукопожатия.

Параметры:

  • ssl_context (OpenSSL.SSL.Context) – Объект контекста PyOpenSSL для входящих соединений.

  • async_fn – Функция обработчика, которая будет вызвана для каждого входящего соединения.

  • *args – Дополнительные аргументы для передачи в функцию обработчика.

close() → None

Закрыть этот сокет и все связанные DTLS-соединения.

Этот объект также может использоваться как менеджер контекста.

class trio.DTLSChannel(*args: object, **kwargs: object)

Bases: Channel[bytes]

Подключение DTLS.

У этого класса нет публичного конструктора — экземпляры получаются, вызывая DTLSEndpoint.serve или connect.

endpoint

Используемый DTLSEndpoint.

peer_address

IP/порт удалённого узла, с которым связано это соединение.

await do_handshake(*, initial_retransmit_timeout: float = 1.0) → None

Выполнить рукопожатие.

Вызов этого метода необязателен — он будет автоматически вызван при первом вызове send или receive. Но явный вызов может быть полезен, если требуется контроль таймаута повторной передачи, использование области отмены для установки общего таймаута на рукопожатие или перехват ошибок при рукопожатии.

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

Параметры:

initial_retransmit_timeout (float) –

Поскольку UDP — ненадежный протокол, возможно, что некоторые пакеты, отправленные во время рукопожатия, будут утеряны. Для обработки этого DTLS использует таймер для автоматической повторной передачи пакетов рукопожатия, которые не получают ответа. Это позволяет установить таймаут, используемый для обнаружения потери пакетов. В идеале он должен быть установлен примерно в 1,5 раза больше времени кругового обмена с удалённым узлом, но 1 секунда — разумное значение по умолчанию. Здесь есть полезные рекомендации: https://tlswg.org/dtls13-spec/draft-ietf-tls-dtls13.html#name-timer-values.

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

await send(data: bytes) → None

Отправить пакет данных безопасно.

await receive() → bytes

Получить следующий пакет данных от удалённого узла, ожидая при необходимости.

Это безопасно вызывать из нескольких задач одновременно, если есть такая необходимость. И что важнее, это безопасно для отмены, что означает, что отмена вызова receive никогда не приведёт к потере пакета или нарушению основного соединения.

close() → None

Закрыть это соединение.

DTLSChannel на самом деле не владеют ресурсами на уровне ОС — сокет принадлежит DTLSEndpoint, а не отдельным соединениям. Поэтому вы не обязаны вызывать этот метод. Однако он прервёт любые другие задачи, вызывающие receive, с ClosedResourceError и приведёт к отказу будущих попыток использования этого соединения.

Также можно использовать этот объект как контекстный менеджер для синхронного или асинхронного использования.

await aclose() → None

Закрыть это соединение асинхронно.

Это включено для соответствия контракту trio.abc.Channel. Оно идентично close, но асинхронное.

set_ciphertext_mtu(new_mtu: int) → None

Указывает Trio на максимальный объём данных, который может быть отправлен в одном пакете этому узлу.

Trio на самом деле не накладывает этого ограничения — если вы передадите большой пакет в send, мы зашифруем его и попытаемся отправить. Но вызов этого метода имеет два полезных эффекта:

  • Если вызван до выполнения рукопожатия, Trio автоматически фрагментирует сообщения рукопожатия, чтобы они вписались в заданный MTU. Он также может фрагментировать их ещё меньше, если обнаружит признаки потери пакетов, поэтому установка этого значения не требуется для успешного соединения. Но обнаружение потери пакетов происходит только после истечения нескольких таймаутов, поэтому если у вас есть основания полагать, что меньший MTU необходим, вы можете установить это значение, чтобы пропустить эти таймауты и быстрее установить соединение.

  • Он изменяет значение, возвращаемое из get_cleartext_mtu. Поэтому, если у вас есть какое-то приближение к MTU на уровне сети, вы можете использовать это, чтобы определить, какой объём накладных расходов DTLS потребуется для хешей/заполнения/и т. д. и сколько места останется для данных вашего приложения.

Здесь MTU измеряет максимальный размер полезной нагрузки UDP, который, по вашему мнению, можно отправить, объём зашифрованных данных, которые можно передать операционной системе в одном вызове send. Он не должен включать заголовки IP/UDP. Обратите внимание, что оценки MTU операционной системы часто являются MTU уровня канала связи, поэтому вам необходимо вычесть 28 байтов для IPv4 и 48 байтов для IPv6, чтобы получить MTU шифрованного текста.

По умолчанию Trio предполагает MTU 1472 байта для IPv4 и 1452 байта для IPv6, которые соответствуют общему Ethernet MTU 1500 байт с учётом накладных расходов IP/UDP.

get_cleartext_mtu() → int

Возвращает максимальное количество байтов, которые можно передать в одном вызове send, при этом вписываясь в MTU на уровне сети.

Подробнее см. set_ciphertext_mtu.

statistics() → DTLSChannelStatistics

Возвращает объект DTLSChannelStatistics со статистикой по этому соединению.

class trio.DTLSChannelStatistics(incoming_packets_dropped_in_trio: int)

В настоящее время он имеет только один атрибут:

  • incoming_packets_dropped_in_trio (int): Указывает количество входящих пакетов от этого узла, которые Trio успешно получил из сети, но затем потерял, потому что внутренний буфер канала был заполнен. Если это ненулевое значение, возможно, вам стоит чаще вызывать receive, использовать больший incoming_packets_buffer или просто не беспокоиться об этом, поскольку ваш протокол на основе UDP должен быть способен справиться с периодическими потерями пакетов, правильно?

Сеть низкого уровня с trio.socket

Модуль trio.socket предоставляет базовый API для сетей низкого уровня Trio. Если вы выполняете обычные операции с потоковыми подключениями по IPv4/IPv6/сокет Unix-домена, то вам, вероятно, следует придерживаться API высокого уровня, описанного выше. Если вы хотите использовать UDP или экзотические семейства адресов, такие как AF_BLUETOOTH, или хотите получить прямой доступ ко всем особенностям API сетевого стека вашей системы, то вы попали по адресу.

Экспорт верхнего уровня

В целом, API, предоставляемый trio.socket, отражает API стандартной библиотеки socket. Большинство констант (например, SOL_SOCKET) и простых утилит (например, inet_aton()) просто повторно экспортируются без изменений. Однако есть и некоторые отличия, которые описаны здесь.

Во-первых, Trio предоставляет аналоги всем функциям стандартной библиотеки, которые возвращают объекты сокетов; их интерфейс идентичен, за исключением того, что они изменены для возврата объектов сокетов Trio:

trio.socket.socket(family=-1, type=-1, proto=-1, fileno=None)

Создаёт новый сокет Trio, как socket.socket.

Поведение этой функции можно настроить с помощью set_custom_socket_factory().

trio.socket.socketpair(family=None, type=SocketKind.SOCK_STREAM, proto=0)

Аналогично socket.socketpair(), но возвращает пару объектов сокетов Trio.

trio.socket.fromfd(fd, family, type, proto=0)

Аналогично socket.fromfd(), но возвращает объект сокета Trio.

trio.socket.fromshare(data)

Аналогично socket.fromshare(), но возвращает объект сокета Trio.

Кроме того, есть новая функция для прямого преобразования сокета стандартной библиотеки в сокет Trio:

trio.socket.from_stdlib_socket(sock: socket) → SocketType

Преобразует объект сокета стандартной библиотеки socket.socket в объект сокета Trio.

В отличие от socket.socket, trio.socket.socket() — это функция, а не класс; если вы хотите проверить, является ли объект сокетом Trio, используйте isinstance(obj, trio.socket.SocketType).

Для поиска имён Trio предоставляет стандартные функции, но с некоторыми изменениями:

await trio.socket.getaddrinfo(host: bytes | str | None, port: bytes | str | int | None, family: int = 0, type: int = 0, proto: int = 0, flags: int = 0) → list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes]]]

Поиск числового адреса по имени.

Аргументы и возвращаемые значения идентичны socket.getaddrinfo(), за исключением того, что эта версия асинхронна.

Кроме того, trio.socket.getaddrinfo() корректно использует IDNA 2008 для обработки имён доменов, не состоящих из ASCII. (socket.getaddrinfo() использует IDNA 2003, что может давать неверный результат в некоторых случаях и привести к подключению к другому хосту, чем предполагалось; см. bpo-17305.)

Поведение этой функции можно настроить с помощью set_custom_hostname_resolver().

await trio.socket.getnameinfo(sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int) → tuple[str, str]

Поиск имени по числовому адресу.

Аргументы и возвращаемые значения идентичны socket.getnameinfo(), за исключением того, что эта версия асинхронна.

Поведение этой функции можно настроить с помощью set_custom_hostname_resolver().

await trio.socket.getprotobyname(name: str) → int

Поиск номера протокола по имени. (Редко используется.)

Аналогично socket.getprotobyname(), но асинхронно.

Trio намеренно НЕ включает некоторые устаревшие, избыточные или нерабочие функции:

  • gethostbyname(), gethostbyname_ex(), gethostbyaddr(): устаревшие; используйте getaddrinfo() и getnameinfo() вместо них.

  • getservbyport(): устаревшая и содержащая ошибки; вместо этого сделайте:

    _, service_name = await getnameinfo(('127.0.0.1', port), NI_NUMERICHOST)
  • getservbyname(): устаревшая и содержащая ошибки; вместо этого сделайте:

    await getaddrinfo(None, service_name)
  • getfqdn(): устаревшая; используйте getaddrinfo() со флагом AI_CANONNAME.

  • getdefaulttimeout(), setdefaulttimeout(): вместо этого используйте стандартную поддержку Trio для Отмены и таймаутов.

  • В Windows, SO_REUSEADDR не экспортируется, потому что это ловушка: имя такое же, как у Unix SO_REUSEADDR, но семантика различна и очень нерабочая. В очень редких случаях, когда вам действительно нужен SO_REUSEADDR в Windows, к нему всё ещё можно получить доступ из модуля стандартной библиотеки socket.

Объекты сокетов

class trio.socket.SocketType

Примечание

trio.socket.SocketType — это абстрактный класс, и его нельзя создавать напрямую; вы получаете конкретные объекты сокетов, вызывая конструкторы, такие как trio.socket.socket(). Однако вы можете использовать его для проверки, является ли объект сокетом Trio, с помощью isinstance(obj, trio.socket.SocketType).

Объекты сокетов Trio в целом очень похожи на объекты сокетов стандартной библиотеки, но с некоторыми важными отличиями:

Во-первых, и наиболее очевидно, все сделано в стиле Trio: блокирующие методы становятся асинхронными методами, и следующие атрибуты не поддерживаются:

  • setblocking(): сокеты Trio всегда ведут себя как блокирующие сокеты; если вам нужно читать/писать из нескольких сокетов одновременно, создайте несколько задач.

  • settimeout(): вместо этого см. Отмену и таймауты.

  • makefile(): файловая API Python синхронна, поэтому она не может быть реализована на основе асинхронного сокета.

  • sendall(): Можно было бы поддержать, но лучше использовать более высокоуровневый SocketStream, а конкретно его метод send_all(), который также выполняет дополнительную проверку ошибок.

Кроме того, следующие методы похожи на аналогичные в socket.socket, но имеют некоторые особенности, специфичные для Trio:

await connect()

Подключает сокет к удалённому адресу.

Аналогично socket.socket.connect(), но асинхронно.

Предупреждение

Из-за ограничений в API операционной системы, иногда невозможно корректно отменить попытку подключения, когда она уже началась. Если connect() отменён, и не может прервать попытку подключения, то:

  1. принудительно закроет сокет, чтобы предотвратить случайное повторное использование

  2. вызовет Cancelled.

Короче: если connect() отменён, то сокет остаётся в неизвестном состоянии — возможно, открытым, а возможно, закрытым. Единственный разумный способ — закрыть его.

is_readable()

Проверяет, доступен ли сокет для чтения.

sendfile()

Ещё не реализовано!

Мы также отслеживаем дополнительное состояние, так как оно оказывается полезным для trio.SocketStream:

did_shutdown_SHUT_WR

Этот атрибут bool равен True, если вы вызвали sock.shutdown(SHUT_WR) или sock.shutdown(SHUT_RDWR), и False в противном случае.

Следующие методы идентичны своим аналогам в socket.socket, но асинхронны, и те, которые принимают аргументы адресов, требуют предварительно разрешённых адресов:

  • accept()

  • bind()

  • recv()

  • recv_into()

  • recvfrom()

  • recvfrom_into()

  • recvmsg() (если доступно)

  • recvmsg_into() (если доступно)

  • send()

  • sendto()

  • sendmsg() (если доступно)

Все методы и атрибуты, не упомянутые выше, идентичны своим аналогам в socket.socket:

  • family

  • type

  • proto

  • fileno()

  • listen()

  • getpeername()

  • getsockname()

  • close()

  • shutdown()

  • setsockopt()

  • getsockopt()

  • dup()

  • detach()

  • share()

  • set_inheritable()

  • get_inheritable()

Асинхронное ввод-вывод файловой системы

Trio предоставляет встроенные средства для выполнения асинхронных операций с файловой системой, таких как чтение или переименование файла. В целом, мы рекомендуем использовать их вместо обычных синхронных файловых API Python. Но здесь компромиссы несколько тонкие: иногда люди переключаются на асинхронный ввод-вывод, а затем удивляются и теряются, обнаружив, что это не ускоряет их программу. Следующий раздел объясняет теорию асинхронного файлового ввода-вывода, чтобы помочь вам лучше понять поведение вашего кода. Или, если вы просто хотите начать работу, вы можете перейти к обзору API.

Предыстория: почему асинхронный файловый ввод-вывод полезен? Ответ может вас удивить

Многие ожидают, что переключение с синхронного файлового ввода-вывода на асинхронный всегда ускорит их программу. Это не так! Если мы просто посмотрим на общую пропускную способность, то асинхронный файловый ввод-вывод может быть быстрее, медленнее или примерно таким же, и это зависит сложным образом от таких вещей, как ваши точные шаблоны доступа к диску или количество оперативной памяти. Основная мотивация асинхронного файлового ввода-вывода — не повышение пропускной способности, а снижение частоты латентных сбоев.

Чтобы понять почему, вам нужно знать две вещи.

Во-первых, в настоящее время ни одна основная операционная система не предлагает универсальный, надежный, собственный API для асинхронных операций с файлами или файловой системой, поэтому нам приходится имитировать его, используя потоки (в частности, trio.to_thread.run_sync()). Это дешево, но не бесплатно: на типичном ПК отправка в рабочий поток добавляет примерно ~100 мкс накладных расходов на каждую операцию. («мкс» произносится как «микросекунды», и в одной секунде 1 000 000 мкс. Обратите внимание, что все числа здесь являются приблизительными порядками величины, чтобы дать вам представление о масштабе; если вам нужны точные числа для вашей среды, измерьте!)

Во-вторых, стоимость операции с диском невероятно бимодальна. Иногда необходимые данные уже кэшируются в оперативной памяти, и тогда доступ к ним очень, очень быстрый — вызов метода read для кэшированного файла занимает порядка ~1 мкс. Но когда данных нет в кэше, доступ к ним намного медленнее: среднее значение составляет ~100 мкс для твердотельных накопителей и ~10 000 мкс для жестких дисков, а если посмотреть на хвостовые задержки, то для обоих типов хранилищ вы увидите случаи, когда иногда какая-то операция будет в 10 или 100 раз медленнее среднего. И это при условии, что ваша программа — единственное, что пытается использовать этот диск — если вы находитесь на каком-то перегруженном виртуальном сервере в облаке, сражаясь за ввод-вывод с другими арендаторами, то кто знает, что произойдет. И некоторые операции могут потребовать нескольких обращений к диску.

Объединяя эти факты: если данные находятся в оперативной памяти, должно быть ясно, что использование потока — ужасная идея — если вы добавите 100 мкс накладных расходов к операции в 1 мкс, то это замедление в 100 раз! С другой стороны, если ваши данные находятся на жестком диске, то использование потока — это отлично — вместо блокировки основного потока и всех задач на 10 000 мкс, мы блокируем их только на 100 мкс и можем потратить остальное время на выполнение других задач для выполнения полезной работы, что фактически может привести к ускорению в 100 раз.

Но вот проблема: для любой отдельной операции ввода-вывода нет способа заранее узнать, будет ли она одной из быстрых или одной из медленных, поэтому нельзя выбирать и отбирать. При переключении на асинхронный файловый ввод-вывод все быстрые операции замедляются, а все медленные ускоряются. Это выигрыш? С точки зрения общей скорости, трудно сказать: это зависит от того, какие диски вы используете и от того, насколько эффективно ваш ядро кэширует данные диска, что, в свою очередь, зависит от ваших шаблонов доступа к файлам, от того, сколько свободной оперативной памяти у вас есть, от нагрузки на вашу систему… и т.д. Если ответ для вас важен, то нет замены измерению фактического поведения вашего кода в вашей реальной среде развертывания. Но что мы можем сказать, так это то, что асинхронный ввод-вывод с диска делает производительность гораздо более предсказуемой в более широком диапазоне условий выполнения.

Если вы не уверены, что делать, мы рекомендуем использовать асинхронный ввод-вывод с диска по умолчанию, потому что это делает ваш код более устойчивым при плохих условиях, особенно в отношении хвостовых задержек; это повышает вероятность того, что то, что видят ваши пользователи, соответствует тому, что вы видели при тестировании. Блокировка основного потока останавливает все задачи на это время. 10 000 мкс — это 10 мс, и не нужно много 10-миллисекундных сбоев, чтобы начать накапливать реальные деньги; асинхронный ввод-вывод с диска может помочь предотвратить это. Просто не ждите чуда и осознавайте компромиссы.

Обзор API

Если вы хотите выполнять общие операции с файловой системой, такие как создание и перечисление каталогов, переименование файлов или проверку метаданных файлов — или если вы просто хотите удобный способ работы с путями к файлам — вам понадобится trio.Path. Это асинхронизированная замена стандартной библиотеки pathlib.Path и предоставляет тот же полный набор операций.

Для чтения и записи в файлы и файлы-подобные объекты Trio также предоставляет механизм для обертывания любого синхронного файла-подобного объекта в асинхронный интерфейс. Если у вас есть объект trio.Path, вы можете получить один из них, вызвав его метод open(); или если вы знаете имя файла, вы можете открыть его напрямую с помощью trio.open_file(). В качестве альтернативы, если у вас уже есть открытый файл-подобный объект, вы можете обернуть его с помощью trio.wrap_file() — один из случаев, где это особенно полезно, — обернуть io.BytesIO или io.StringIO при написании тестов.

Асинхронные объекты пути

class trio.Path(*args: str | os.PathLike[str])

Асинхронный объект pathlib.Path, выполняющий блокирующие методы в trio.to_thread.run_sync().

Создание объекта Path возвращает конкретный подкласс, специфичный для платформы, один из PosixPath или WindowsPath.

await absolute()

Аналогично absolute(), но асинхронно.

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

Используйте resolve(), чтобы получить канонический путь к файлу.

property anchor

Сочетание диска и корня, или ‘’.

as_posix()

Возвращает строковое представление пути с косыми чертами (/).

as_uri()

Возвращает путь как URI «файла».

await chmod(mode, *, follow_symlinks=True)

Аналогично chmod(), но асинхронно.

Изменяет разрешения пути, как os.chmod().

classmethod await cwd()

Аналогично cwd(), но асинхронно.

Возвращает новый путь, указывающий на текущий рабочий каталог (как возвращает os.getcwd()).

property drive

Префикс диска (буква или UNC-путь), если есть.

await exists()

Аналогично exists(), но асинхронно.

Существует ли этот путь.

await expanduser()

Аналогично expanduser(), но асинхронно.

Возвращает новый путь с расширенными конструкциями ~ и ~user (как возвращает os.path.expanduser).

await glob(pattern)

Аналогично glob(), но асинхронно.

Итерируется по поддереву и возвращает все существующие файлы (любого типа, включая каталоги), соответствующие заданному относительному шаблону.

Это асинхронный метод, который возвращает синхронный итератор, поэтому вы используете его так:

for subpath in await mypath.glob():
    ...

Примечание

Итератор загружается в память сразу при первом вызове (см. проблему #501 для обсуждения).

await group()

Аналогично group(), но асинхронно.

Возвращает имя группы файла gid.

await hardlink_to(target)

Аналогично hardlink_to(), но асинхронно.

Создаёт жёсткую ссылку на этот путь, указывающую на тот же файл, что и target.

Обратите внимание на порядок аргументов (self, target) — обратный по сравнению с os.link.

classmethod await home()

Аналогично home(), но асинхронно.

Возвращает новый путь, указывающий на домашний каталог пользователя (как возвращает os.path.expanduser(‘~’)).

is_absolute()

True, если путь абсолютный (имеет корень и, применимо, диск).

await is_block_device()

Аналогично is_block_device(), но асинхронно.

Является ли этот путь блочным устройством.

await is_char_device()

Аналогично is_char_device(), но асинхронно.

Является ли этот путь символьным устройством.

await is_dir()

Аналогично is_dir(), но асинхронно.

Является ли этот путь каталогом.

await is_fifo()

Аналогично is_fifo(), но асинхронно.

Является ли этот путь FIFO.

await is_file()

Аналогично is_file(), но асинхронно.

Является ли этот путь обычным файлом (также True для символьных ссылок, указывающих на обычные файлы).

await is_mount()

Аналогично is_mount(), но асинхронно.

Проверяет, является ли этот путь точкой монтирования POSIX.

is_relative_to(*other)

Возвращает True, если путь является относительным к другому пути, или False.

is_reserved()

Возвращает True, если путь содержит одно из специальных имён, зарезервированных системой, если таковые имеются.

await is_socket()

Аналогично is_socket(), но асинхронно.

Является ли этот путь сокетом.

await is_symlink()

Аналогично is_symlink(), но асинхронно.

Является ли этот путь символьной ссылкой.

await iterdir()

Аналогично iterdir(), но асинхронно.

Итерируется по файлам в этом каталоге. Не возвращает результаты для специальных путей «.» и «..».

Это асинхронный метод, который возвращает синхронный итератор, поэтому вы используете его так:

for subpath in await mypath.iterdir():
    ...

Примечание

Итератор загружается в память сразу при первом вызове (см. проблему #501 для обсуждения).

joinpath(*args)

Объединяет этот путь с одним или несколькими аргументами и возвращает новый путь, представляющий либо подпуть (если все аргументы являются относительными путями), либо совершенно другой путь (если один из аргументов закреплён).

await lchmod(mode)

Аналогично lchmod(), но асинхронно.

Подобно chmod(), но если путь указывает на символическую ссылку, разрешения символьной ссылки изменяются, а не её цели.

await link_to(target)

Аналогично link_to(), но асинхронно.

Создаёт жёсткую ссылку на целевой путь, указывающую на этот путь.

Обратите внимание, что эта функция не создаёт жёсткую ссылку на этот путь, указывающую на target, несмотря на подразумеваемое действие функции и названия аргументов. Порядок аргументов (target, link) обратный по сравнению с Path.symlink_to, но соответствует порядку в os.link.

Устарело начиная с Python 3.10 и планируется к удалению в Python 3.12. Используйте hardlink_to() вместо этого.

END_OF_DOCUMENT_MARKER ```

await lstat()

Как lstat(), но асинхронно.

Аналогично stat(), но если путь указывает на символическую ссылку, возвращаются данные о статусе самой ссылки, а не её целевого объекта.

match(path_pattern)

Возвращает True, если этот путь соответствует заданному шаблону.

await mkdir(mode=511, parents=False, exist_ok=False)

Как mkdir(), но асинхронно.

Создаёт новую директорию по заданному пути.

property name

Конечный компонент пути, если он есть.

await open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)

Как open(), но асинхронно.

Открывает файл, указанный этим путём, и возвращает объект файла, как и встроенная функция open().

await owner()

Как owner(), но асинхронно.

Возвращает имя пользователя владельца файла.

property parent

Логический родительский элемент пути.

property parents

Последовательность логических родительских элементов этого пути.

property parts

Объект, обеспечивающий последовательный доступ к компонентам пути в файловой системе.

await read_bytes()

Как read_bytes(), но асинхронно.

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

await read_text(encoding=None, errors=None)

Как read_text(), но асинхронно.

Открывает файл в текстовом режиме, считывает его и закрывает.

await readlink()

Как readlink(), но асинхронно.

Возвращает путь, на который указывает символическая ссылка.

relative_to(*other)

Возвращает относительный путь к другому пути, определённому переданными аргументами. Если операция невозможна (потому что это не подпуть другого пути), генерирует исключение ValueError.

await rename(target)

Как rename(), но асинхронно.

Переименовывает этот путь в целевой путь.

Целевой путь может быть абсолютным или относительным. Относительные пути интерпретируются относительно текущей рабочей директории, а не директории объекта Path.

Возвращает новый объект Path, указывающий на целевой путь.

await replace(target)

Как replace(), но асинхронно.

Переименовывает этот путь в целевой путь, перезаписывая, если целевой путь существует.

Целевой путь может быть абсолютным или относительным. Относительные пути интерпретируются относительно текущей рабочей директории, а не директории объекта Path.

Возвращает новый объект Path, указывающий на целевой путь.

await resolve(strict=False)

Как resolve(), но асинхронно.

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

await rglob(pattern)

Как rglob(), но асинхронно.

Рекурсивно возвращает все существующие файлы (любого типа, включая директории), соответствующие заданному относительному шаблону, где-либо в этом поддереве.

Это асинхронный метод, возвращающий синхронный итератор, поэтому вы используете его так:

for subpath in await mypath.rglob():
    ...

Примечание

Итератор загружается в память сразу при первом вызове (см. issue #501 для обсуждения).

await rmdir()

Как rmdir(), но асинхронно.

Удаляет эту директорию. Директория должна быть пустой.

property root

Корень пути, если он есть.

await samefile(other_path)

Как samefile(), но асинхронно.

Возвращает, является ли other_path тем же файлом, что и этот (как возвращает os.path.samefile()).

await stat(*, follow_symlinks=True)

Как stat(), но асинхронно.

Возвращает результат системного вызова stat() для этого пути, как делает os.stat().

property stem

Конечный компонент пути без последнего суффикса.

property suffix

Последний суффикс конечного компонента, если он есть.

Включает ведущую точку. Например: ‘.txt’

property suffixes

Список суффиксов конечного компонента, если они есть.

Включают ведущие точки. Например: [‘.tar’, ‘.gz’]

await symlink_to(target, target_is_directory=False)

Как symlink_to(), но асинхронно.

Создаёт символическую ссылку на этот путь, указывающую на целевой путь. Обратите внимание, что порядок аргументов (ссылка, цель) обратный по сравнению с os.symlink.

await touch(mode=438, exist_ok=True)

Как touch(), но асинхронно.

Создаёт этот файл с заданным режимом доступа, если он не существует.

await unlink(missing_ok=False)

Как unlink(), но асинхронно.

Удаляет этот файл или ссылку. Если путь указывает на директорию, используйте rmdir() вместо этого.

with_name(name)

Возвращает новый путь с изменённым именем файла.

with_stem(stem)

Возвращает новый путь с изменённым значением stem.

with_suffix(suffix)

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

await write_bytes(data)

Как write_bytes(), но асинхронно.

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

await write_text(data, encoding=None, errors=None, newline=None)

Как write_text(), но асинхронно.

Открывает файл в текстовом режиме, записывает в него и закрывает.

class trio.PosixPath(*args: str | os.PathLike[str])

Асинхронный pathlib.PosixPath, который выполняет блокирующие методы в trio.to_thread.run_sync().

class trio.WindowsPath(*args: str | os.PathLike[str])

Асинхронный pathlib.WindowsPath, который выполняет блокирующие методы в trio.to_thread.run_sync().

Асинхронные объекты файлов

await trio.open_file(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=None, opener=None)

Асинхронная версия open().

Возвращает:

Объект асинхронного файла

Пример:

async with await trio.open_file(filename) as f:
    async for line in f:
        pass

assert f.closed

См. также

trio.Path.open()

trio.wrap_file(file)

Оборачивает любой объект файла в обёртку, предоставляющую интерфейс асинхронного объекта файла.

Параметры:

file – объект файла

Возвращает:

Объект асинхронного файла, который оборачивает file

Пример:

async_file = trio.wrap_file(StringIO('asdf'))

assert await async_file.read() == 'asdf'

Asynchronous file interface

Асинхронные объекты файлов Trio имеют интерфейс, который автоматически адаптируется к объекту, который оборачивается. Интуитивно, вы можете в основном рассматривать их как обычный объект файла, за исключением добавления await перед любыми методами, выполняющими ввод-вывод. Определение объекта файла в Python немного неясно, поэтому вот детали:

  • Синхронные атрибуты/методы: если присутствуют следующие атрибуты или методы, они переэкспортируются без изменений: closed, encoding, errors, newlines, isatty, readable, seekable, writable, buffer, raw, line_buffering, closefd, name, mode, getvalue, getbuffer.

  • Асинхронные методы: если присутствуют следующие методы, они переэкспортируются как асинхронные методы: flush, read, read1, readall, readinto, readline, readlines, seek, tell, truncate, write, writelines, readinto1, peek, detach.

Особые заметки:

  • Объекты асинхронных файлов реализуют интерфейс Trio AsyncResource: их закрывают, вызывая aclose() вместо close (!!), и они могут использоваться как асинхронные контекстные менеджеры. Как и все методы aclose(), метод aclose для асинхронных объектов файлов гарантированно закрывает файл перед возвратом, даже если он отменён или по какой-либо причине вызывает ошибку.

  • Использование одного и того же асинхронного объекта файла из нескольких задач одновременно: поскольку асинхронные методы асинхронных объектов файлов реализуются с помощью потоков, безопасно вызывать два из них одновременно из разных задач ТОЛЬКО если лежащий в основе синхронный объект файла является потокобезопасным. Вы должны обратиться к документации для объекта, который вы оборачиваете. Для объектов, возвращаемых из trio.open_file() или trio.Path.open(), это зависит от того, открываете ли вы файл в двоичном или текстовом режиме: файлы в двоичном режиме безопасны для задач/потоков, файлы в текстовом режиме — нет.

  • Асинхронные объекты файлов могут использоваться как асинхронные итераторы для перебора строк файла:

    async with await trio.open_file(...) as f:
        async for line in f:
            print(line)
  • Метод detach, если он присутствует, возвращает асинхронный объект файла.

Это должно включать все атрибуты, экспонируемые классами в io. Но если вы оборачиваете объект, у которого есть другие атрибуты, которых нет в этом списке, вы можете получить к ним доступ через атрибут .wrapped:

wrapped

Основной синхронный объект файла.

Запуск дочерних процессов

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

В большинстве случаев это делается через наш интерфейс высокого уровня, trio.run_process. Он позволяет либо запустить процесс до завершения, при этом необязательно захватив вывод, либо запустить его в фоновом задании и взаимодействовать с ним во время его выполнения:

await trio.run_process(command: str | bytes | os.PathLike | Sequence[str | bytes | os.PathLike], *, stdin: bytes | bytearray | memoryview | int | HasFileno | None = b'', capture_stdout: bool = False, capture_stderr: bool = False, check: bool = True, deliver_cancel: Callable[[Process], Awaitable[object]] | None = None, task_status: TaskStatus[Process] = TASK_STATUS_IGNORED, **options: object) → subprocess.CompletedProcess[bytes]

Запустить command в дочернем процессе и дождаться его завершения.

Эта функция может быть вызвана двумя способами.

Один вариант — прямой вызов, например:

completed_process_info = await trio.run_process(...)

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

Другой вариант — запустить его как задачу с помощью Nursery.start — улучшенной версии start_soon, которая позволяет задаче возвращать значение во время запуска:

process = await nursery.start(trio.run_process, ...)

В этом случае start возвращает объект Process, который можно использовать для взаимодействия с процессом во время его работы. Используйте это, если хотите обращаться с процессом как с фоновой задачей.

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

Входные данные: run_process поддерживает все те же stdin= аргументы, что и subprocess.Popen. Кроме того, если вы просто хотите передать некоторые фиксированные данные, вы можете передать обычный объект bytes, и run_process позаботится о настройке канала, подаче переданных данных и отправке конца файла. По умолчанию это b"", что означает, что дочерний процесс получит пустой stdin. Если вы хотите, чтобы дочерний процесс читал со стандартного ввода родительского процесса, используйте stdin=None.

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

При прямом вызове run_process вы можете захватить вывод дочернего процесса, передав capture_stdout=True для захвата стандартного вывода дочернего процесса и/или capture_stderr=True для захвата его стандартной ошибки. Захваченные данные собираются Trio в буфер оперативной памяти, а затем предоставляются как атрибуты stdout и/или stderr возвращенного объекта CompletedProcess. Значение для любого потока, который не был захвачен, будет None.

Если вы хотите захватить как stdout, так и stderr, сохраняя их раздельно, передайте capture_stdout=True, capture_stderr=True.

Если вы хотите захватить как stdout, так и stderr, но перемешать их в порядке их вывода, используйте: capture_stdout=True, stderr=subprocess.STDOUT. Это перенаправляет stderr дочернего процесса в его stdout, поэтому объединенный вывод будет доступен в атрибуте stdout.

Если вы используете await nursery.start(trio.run_process, ...) и хотите захватить вывод дочернего процесса для дальнейшей обработки, используйте stdout=subprocess.PIPE, а затем убедитесь, что вы прочитали данные из потока Process.stdout. Если вы хотите захватить stderr отдельно, используйте stderr=subprocess.PIPE. Если вы хотите захватить оба потока, но перемешанными в правильном порядке, используйте stdout=subprocess.PIPE, stderr=subprocess.STDOUT.

Проверка ошибок: Если дочерний процесс завершается с ненулевым кодом состояния, что указывает на ошибку, run_process() генерирует исключение subprocess.CalledProcessError вместо нормального возврата. Захваченные выводы по-прежнему доступны как атрибуты stdout и stderr этого исключения. Чтобы отключить это поведение, чтобы run_process() возвращался нормально, даже если дочерний процесс завершился аномально, передайте check=False.

Обратите внимание, что это может сделать аргументы capture_stdout и capture_stderr полезными даже при запуске run_process как задачи: если вам нужен вывод только в случае сбоя процесса, вы можете включить захват и затем прочитать вывод из CalledProcessError.

Отмена: При отмене run_process отправляет запрос на завершение дочернему процессу, а затем ждёт его полного завершения. Аргумент deliver_cancel позволяет управлять способом завершения процесса.

Примечание

run_process преднамеренно аналогичен стандартной библиотеке subprocess.run, но некоторые значения по умолчанию отличаются. В частности, мы используем:

  • check=True, потому что “ошибки никогда не должны проходить молча / если только они не будут явно умолкнуты”.

  • stdin=b"", потому что это даёт менее запутанные результаты, если дочерний процесс неожиданно пытается прочитать со stdin.

Чтобы получить семантику subprocess.run, используйте check=False, stdin=None.

Параметры:

  • command (список или строка) – Команда для выполнения. Обычно это последовательность строк, например, ['ls', '-l', 'directory with spaces'], где первый элемент указывает исполняемый файл, а остальные — его аргументы. В **options или на Windows, command может быть строкой, которая будет обработана в соответствии с платформенно-зависимыми правилами цитирования.

  • stdin (bytes, subprocess.PIPE, дескриптор файла или None) –

    байты, которые нужно передать подпроцессу в стандартный поток ввода или None, если стандартный поток ввода подпроцесса должен поступать оттуда же, откуда стандартный поток ввода родительского процесса Trio. Как и в случае с модулем subprocess, вы также можете передать дескриптор файла или объект с методом fileno(), в этом случае стандартный поток ввода подпроцесса будет поступать из этого файла.

    При запуске run_process как фоновой задачи, вы также можете использовать stdin=subprocess.PIPE, в этом случае Process.stdin будет SendStream, который вы можете использовать для отправки данных в дочерний процесс.

  • capture_stdout (bool) – Если True, то захватывает байты, которые подпроцесс записывает в стандартный поток вывода, и возвращает их в атрибуте stdout возвращаемого объекта subprocess.CompletedProcess или subprocess.CalledProcessError.

  • capture_stderr (bool) – Если True, то захватывает байты, которые подпроцесс записывает в стандартный поток ошибок, и возвращает их в атрибуте stderr возвращаемого объекта CompletedProcess или subprocess.CalledProcessError.

  • check (bool) – Если False, то не проверяет, завершился ли подпроцесс успешно. Вы должны убедиться в проверке атрибута returncode возвращаемого объекта, если вы передаете check=False, чтобы ошибки не пропускались незамеченными.

  • deliver_cancel (асинхронная функция или None) –

    Если run_process отменено, то необходимо убить дочерний процесс. Существует несколько способов сделать это, поэтому мы позволяем вам настроить его.

    Если вы передаете None (по умолчанию), то поведение зависит от платформы:

    • В Windows Trio вызывает TerminateProcess, которая должна немедленно убить процесс.

    • В Unix-подобных системах по умолчанию происходит отправка SIGTERM, ожидание 5 секунд и отправка SIGKILL.

    В качестве альтернативы можно настроить это поведение, передав произвольную асинхронную функцию, которая будет вызвана с объектом Process в качестве аргумента. Например, стандартное поведение в Unix можно реализовать так:

    async def my_deliver_cancel(process):
        process.send_signal(signal.SIGTERM)
        await trio.sleep(5)
        process.send_signal(signal.SIGKILL)

    Когда процесс фактически завершается, функция deliver_cancel автоматически отменяется — поэтому, если процесс завершается после SIGTERM, мы никогда не достигнем SIGKILL.

    В любом случае run_process всегда будет ждать завершения дочернего процесса перед поднятием Cancelled.

  • **options – run_process() также принимает любые общие параметры подпроцесса и передает их конструктору Process. Это включает параметры stdout и stderr, которые предоставляют дополнительные возможности перенаправления, такие как stderr=subprocess.STDOUT, stdout=subprocess.DEVNULL или дескрипторы файлов.

Возвращаемое значение:

При обычном вызове — экземпляр subprocess.CompletedProcess, описывающий код возврата и выходные данные.

При вызове через Nursery.start — экземпляр trio.Process.

Исключения:

  • UnicodeError – если stdin указано как строка Unicode, а не байты

  • ValueError – если для одного и того же потока указано несколько перенаправлений, например, и capture_stdout=True, и stdout=subprocess.DEVNULL

  • subprocess.CalledProcessError – если check=False не передано и процесс завершился с ненулевым кодом возврата

  • OSError – если при запуске или взаимодействии с процессом произошла ошибка

  • ExceptionGroup – если в deliver_cancel произошли исключения или когда при взаимодействии с подпроцессом произошли исключения. Если strict_exception_groups установлено в значение False в глобальном контексте, что устарело, то отдельные исключения будут объединены.

Примечание

Дочерний процесс выполняется в той же группе процессов, что и родительский процесс Trio, поэтому Ctrl+C одновременно передается как родительскому, так и дочернему процессу. Если вы не хотите этого поведения, обратитесь к документации вашей платформы по запуску дочерних процессов в другой группе процессов.

class trio._subprocess.HasFileno(Protocol)

Представляет любой файлоподобный объект, имеющий дескриптор файла.

fileno() → int

trio._subprocess.StrOrBytesPath

псевдоним str | bytes | PathLike[str] | PathLike[bytes]

class trio.Process

Поток-дочерний процесс. Подобно subprocess.Popen, но асинхронный.

У этого класса нет публичного конструктора. Наиболее распространённый способ получить объект Process — это объединить Nursery.start с run_process:

process_object = await nursery.start(run_process, ...)

Таким образом, run_process контролирует процесс и гарантирует его надлежащее завершение, а также (необязательно) проверяет возвращаемое значение, подаёт на вход данные и т. д.

Если вам нужен больший контроль — например, потому что вы хотите запустить дочерний процесс, который переживёт вашу программу — то ещё одним вариантом является использование trio.lowlevel.open_process:

process_object = await trio.lowlevel.open_process(...)

args

command, переданный при создании, определяющий исполняемый процесс и его аргументы.

Тип:

str или list

pid

Идентификатор процесса (PID) управляемого этим объектом дочернего процесса.

Тип:

int

stdin

Поток, подключенный к стандартному потоку ввода дочернего процесса: при записи байтов в него, они становятся доступными для чтения дочерним процессом. Доступен только если Process был создан с использованием stdin=PIPE; в противном случае будет None.

Тип:

trio.abc.SendStream или None

stdout

Поток, подключенный к стандартному потоку вывода дочернего процесса: когда дочерний процесс записывает в стандартный вывод, записанные байты становятся доступны для чтения здесь. Доступен только если Process был создан с использованием stdout=PIPE; в противном случае будет None.

Тип:

trio.abc.ReceiveStream или None

stderr

Поток, подключенный к стандартному потоку ошибок дочернего процесса: когда дочерний процесс записывает в стандартный вывод ошибок, записанные байты становятся доступны для чтения здесь. Доступен только если Process был создан с использованием stderr=PIPE; в противном случае будет None.

Тип:

trio.abc.ReceiveStream или None

stdio

Поток, отправляющий данные в стандартный поток ввода дочернего процесса и получающий их из стандартного потока вывода. Доступен только если оба stdin и stdout доступны; в противном случае будет None.

Тип:

trio.StapledStream или None

returncode

Код завершения процесса (целое число) или None, если он всё ещё выполняется.

По соглашению, код возврата 0 означает успех. В системах UNIX отрицательные значения указывают на завершение из-за сигнала, например, -11 при завершении сигналом 11 (SIGSEGV). В Windows, процесс, завершившийся из-за вызова Process.terminate(), будет иметь код завершения 1.

В отличие от стандартной библиотеки subprocess.Popen.returncode, вам не нужно вызывать poll или wait для обновления этого атрибута; он автоматически обновляется по мере необходимости и всегда предоставляет последнюю информацию.

await wait() → int

Ожидать завершения процесса.

Возвращает:

Код завершения процесса; см. returncode.

poll() → int | None

Возвращает код завершения процесса (целое число) или None, если он всё ещё выполняется.

Обратите внимание, что в Trio (в отличие от стандартной библиотеки subprocess.Popen), process.poll() и process.returncode всегда возвращают один и тот же результат. См. returncode для получения более подробной информации. Этот метод включён только для облегчения переноса кода из subprocess.

kill() → None

Немедленно завершить процесс.

В системах UNIX это эквивалентно send_signal(signal.SIGKILL). В Windows вызывается TerminateProcess. В обоих случаях процесс не может предотвратить своё убийство, но завершение будет доставлено асинхронно; используйте wait(), если вы хотите убедиться, что процесс действительно завершён, прежде чем продолжать.

terminate() → None

Завершить процесс вежливо, если это возможно.

В системах UNIX это эквивалентно send_signal(signal.SIGTERM); по соглашению это запрос на вежливое завершение, но плохо написанный или неисправный процесс может его проигнорировать. В Windows terminate() принудительно завершает процесс так же, как kill().

send_signal(sig: signal.Signals | int) → None

Отправить сигнал sig процессу.

В системах UNIX, sig может быть любым сигналом, определённым в модуле signal, например, signal.SIGINT или signal.SIGTERM. В Windows это может быть всё, что принимает стандартная библиотека subprocess.Popen.send_signal().

Примечание

communicate() не предоставляется как метод для объектов Process; вызывайте run_process() обычно для простого захвата или напишите цикл сами, если у вас есть особые потребности. communicate() имеет довольно необычное поведение отмены в стандартной библиотеке (в некоторых системах он запускает фоновый поток, который продолжает читать из дочернего процесса даже после истечения срока ожидания), и мы хотели предоставить интерфейс с меньшим количеством неожиданностей.

Если trio.run_process слишком ограничивает, мы также предлагаем низкоуровневый API, trio.lowlevel.open_process. Например, если вы хотите запустить дочерний процесс, который переживёт родительский процесс и станет сиротой, то run_process этого сделать не может, но open_process может.

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

Все API Trio для работы с дочерними процессами принимают множество ключевых аргументов, используемых стандартным модулем subprocess для управления средой запуска процесса и механизмами коммуникации с ним. Эти параметры могут быть переданы там, где в документации ниже вы видите **options. Полный список см. здесь, а часто используемые — здесь в документации subprocess. (Возможно, вам потребуется import subprocess для доступа к константам, таким как PIPE или DEVNULL.)

В настоящее время Trio всегда использует небуферизованные потоки байтов для общения с процессом, поэтому он не поддерживает параметры encoding, errors, universal_newlines (псевдоним text) и bufsize.

Цитата: больше, чем вы хотели знать

Команда для запуска и её аргументы обычно должны передаваться API Trio для работы с подпроцессами как последовательность строк, где первый элемент последовательности задаёт команду для запуска, а оставшиеся элементы — её аргументы, по одному аргументу на элемент. Эта форма используется, чтобы избежать потенциальных проблем с цитированием; например, вы можете запустить ["cp", "-f", source_file, dest_file], не беспокоясь о том, содержит ли source_file или dest_file пробелы.

Если вы запускаете подпроцессы без shell=True и на UNIX-системах, то это всё, что вам нужно знать о задании команды. Если вы используете shell=True или работаете на Windows, вам, вероятно, стоит прочитать остальную часть этого раздела, чтобы быть осведомлённым о потенциальных проблемах.

При использовании shell=True на UNIX, вы должны указать команду как одну строку, которая будет передана оболочке так, как будто вы ввели её в интерактивную командную строку. Преимущество этого варианта заключается в том, что он позволяет использовать возможности оболочки, такие как конвейеры и перенаправление, без написания кода для их обработки. Например, вы можете написать Process("ls | grep some_string", shell=True). Недостаток заключается в том, что вам необходимо учитывать правила цитирования оболочки, обычно заключая в shlex.quote() любой аргумент, который может содержать пробелы, кавычки или другие метасимволы оболочки. Если вы этого не сделаете, ваш, казалось бы, безопасный f"ls | grep {some_string}" может привести к катастрофе при вызове с some_string = "foo; rm -rf /".

В Windows основной API для запуска процессов (система вызовов CreateProcess()) принимает строку, а не список, и на самом деле дочернему процессу решать, как он хочет разбить эту строку на отдельные аргументы. Поскольку язык C определяет, что main() должен принимать список аргументов, большинство программ, с которыми вы сталкиваетесь, будут следовать правилам, используемым Microsoft C/C++ runtime. subprocess.Popen, и, следовательно, Trio, используют эти правила при преобразовании последовательности аргументов в строку, и они документированы вместе с модулем subprocess. Нет документированной функции стандартной библиотеки Python, которая может напрямую выполнить это преобразование, поэтому даже в Windows вы почти всегда хотите передавать последовательность аргументов, а не строку. Но если программа, которую вы запускаете, не разбивает свою командную строку обратно на отдельные аргументы стандартным способом, вам может потребоваться передать строку, чтобы обойти это. (Или вам просто не повезёт: насколько я могу судить, просто нет способа передать аргумент, содержащий двойную кавычку, в пакетный файл Windows.)

В Windows с shell=True всё становится ещё более хаотичным. Теперь применяются два отдельных набора правил цитирования: один — оболочкой командной строки Windows CMD.EXE, а другой — запускаемым процессом, и они разные. (И нет shlex.quote(), чтобы спасти вас: она использует правила цитирования для UNIX, даже в Windows.) Большинство специальных символов, интерпретируемых оболочкой &<>()^|, не обрабатываются как специальные, если оболочка считает их находящимися внутри двойных кавычек, но подстановки переменных среды %FOO% по-прежнему выполняются, и оболочка не предоставляет способ написать двойную кавычку внутри двойных кавычек. За пределами двойных кавычек любой символ (включая двойную кавычку) можно экранировать, добавив ведущий ^. Но поскольку конвейер обрабатывается запуском каждой команды конвейера в подоболочке, может потребоваться несколько уровней экранирования:

echo ^^^&x | find "x" | find "x"          # prints: &x

И если вы объедините конвейеры с группировкой (), вам может понадобиться ещё больше уровней экранирования:

(echo ^^^^^^^&x | find "x") | find "x"    # prints: &x

Поскольку создание процесса принимает одну строку аргументов, цитирование CMD.EXE не влияет на разбиение слов, а двойные кавычки не удаляются во время прохода расширения CMD.EXE. Двойные кавычки проблематичны, поскольку CMD.EXE обрабатывает их иначе, чем правила MSVC runtime; в:

prog.exe "foo \"bar\" baz"

программа увидит один аргумент foo "bar" baz, но CMD.EXE считает, что bar\ не заключён в кавычки, в то время как foo \ и baz заключены. Всё это делает надёжную интерполяцию чего-либо в командную строку shell=True в Windows сложной задачей, и Trio использует поведение subprocess: если вы передаёте последовательность с shell=True, она цитируется так же, как последовательность с shell=False, и лучше не содержать никаких метасимволов оболочки, которые вы не планировали.

Дополнительные материалы:

  • https://stackoverflow.com/questions/30620876/how-to-properly-escape-filenames-in-windows-cmd-exe

  • https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts

Сигналы

with trio.open_signal_receiver(*signals: signal.Signals | int) → Generator[AsyncIterator[int], None, None] as signal_aiter

Менеджер контекста для перехвата сигналов.

Вхождение в этот менеджер контекста начинает прослушивание заданных сигналов и возвращает асинхронный итератор; выход из менеджера контекста останавливает прослушивание.

Асинхронный итератор блокируется до получения сигнала, а затем возвращает его.

Обратите внимание, что если вы выйдете из блока with, а итератор ещё не обработал ожидающие сигналы, они будут повторно доставлены с помощью стандартной обработки сигналов Python. Это предотвращает гонку при получении сигнала непосредственно перед выходом из блока with.

Параметры:

signals — сигналы для прослушивания.

Возможные исключения:

  • TypeError — если сигналы не были предоставлены.

  • RuntimeError — если вы пытаетесь использовать это в другом потоке, кроме основного потока Python. (Это ограничение Python.)

Пример

Общей практикой для демонов Unix является перезагрузка конфигурации при получении сигнала SIGHUP. Вот пример того, как это можно сделать с помощью open_signal_receiver():

with trio.open_signal_receiver(signal.SIGHUP) as signal_aiter:
    async for signum in signal_aiter:
        assert signum == signal.SIGHUP
        reload_configuration()

© 2017 Nathaniel J. Smith
Licensed under the MIT License.
https://trio.readthedocs.io/en/v0.29.0/reference-io.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API