понедельник, 5 декабря 2022 г.

[prog.python.wtf] Понадобилось мне давеча разобраться с Python-овским io... И как-то оно что-то неожиданно непонятно. Вот совсем

В C++ приложение встраивается Python чтобы исполнять Python-вский скрипт прямо внутри приложения. Потребовалось сделать так, чтобы все, что Python-овский скрипт печатает в sys.stdout, отправлялось в C++ную часть.

Стал разбираться с Python-овским io.

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

Мало что понял.

Что удивило. Т.к. пока читаешь, то вроде бы и буквы все понятные, и в слова понятные они складываются. А вот что к чему и почему... Все никак.

Гуглил, читал Stackoverflow, много думал :)

Но просветление не приходило.

Даже в исходники CPython заглянул, чтобы понять, как же вся эта кухня с IOBase, RawIOBase, BufferedIOBase и TextIOBase работает.

В общем, пришел к выводу, что мне нужен стандартный io.TextIOWrapper. В конструктор ему передается экземпляр стандартного io.BufferedWriter. А уже в конструктор io.BufferedWriter в качестве параметра raw передается экземпляр моего класса, наследника io.RawIOBase. Что-то вроде:

import io
import sys

class MyRawIOBase(io.RawIOBase):
    def __init__(self):
        self.dest = open(sys.stdout.fileno(), 'wb'0)

    def write(self, b, /):
        self.dest.write(b)

    def writable(self):
        return True

sys.stdout = io.TextIOWrapper(io.BufferedWriter(MyRawIOBase()), write_through=True)

print('Hello, World!')

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

Hello, World!
Hello, World!
Exception ignored in: <_io.TextIOWrapper encoding='UTF-8'>
BlockingIOError: [Errno 0] write could not complete without blocking
Hello, World!
Hello, World!

Могу предположить, что все это происки io.BufferedWriter. Но почему это происходит и как поправить -- вот непонятно совершенно.

В этот момент я уже откровенно впал в ступор.

Прежде чем отправиться в Гугл за следующей порцией информации решил проверить, а что будет, если в io.TextIOWrapper отдать мой MyRawIOBase напрямую, без промежуточных io.BufferedWriter:

import io
import sys

class MyRawIOBase(io.RawIOBase):
    def __init__(self):
        self.dest = open(sys.stdout.fileno(), 'wb'0)

    def write(self, b, /):
        self.dest.write(b)

    def writable(self):
        return True

sys.stdout = io.TextIOWrapper(MyRawIOBase(), write_through=True)

print('Hello, World!')

И, о чудо, оно заработало!

На чем-то подобном и остановился.

Хотя есть подозрение, что все-таки делаю что-то не так, и что io.BufferedWriter таки нужен. Но вот куда копать не имею совершенно никакого понятия.

PS. А стандартная документация по Python-у таки производит странное впечатление. Как уже и говорил, пока читаешь, то вроде как все понятно. А как пытаешься применить прочитанное, то выясняется, что ничего полезного-то и не вычитал. Возможно, это все следствие того, что в Python-е я совершенный новичок.

2 комментария:

Stanislav Mischenko комментирует...

> io.BufferedWriter таки нужен

А зачем?

У меня вот есть идея, но если что, я то же не "питонист", а только учусь ;)

> open(sys.stdout.fileno(), 'wb', 0)

Вот здесь вместо 0 можно поставить размер буфера и получите буфер "нахаляву".

eao197 комментирует...

> А зачем?

Потому что в официальной документации к TextIOWrapper говорится: "A buffered text stream providing higher-level access to a BufferedIOBase buffered binary stream. It inherits TextIOBase."

Так что я предполагаю, что первым параметром к TextIOWrapper ожидается какой-то из BufferedIOBase. Но, возможно, ошибаюсь.

> Вот здесь вместо 0 можно поставить размер буфера и получите буфер "нахаляву".

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