Метод BytesIO.truncate не расширяет содержимое буфера

В документации метода IOBase.truncate говорится, что:

обрезать (размер = нет)

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

Изменено в версии 3.5: Windows теперь будет заполнять файлы нулями при расширении.

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

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

from io import BytesIO

# prints b'\x00\x00\x00\x00\x00\x00\x00\x00'
data = BytesIO(8 * b"\x00")
print(data.getvalue())

# prints 16
print(data.truncate(16))

# prints b'\x00\x00\x00\x00\x00\x00\x00\x00'
print(data.getvalue())

# prints b'\x00\x00\x00\x00\x00\x00\x00\x00'
print(bytes(data.getbuffer()))

Где я свернул не туда?


person Deedee Megadoodoo    schedule 17.12.2019    source источник


Ответы (1)


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

static PyObject *_io_BytesIO_truncate_impl(bytesio *self, Py_ssize_t size)
/*[clinic end generated code: output=9ad17650c15fa09b input=423759dd42d2f7c1]*/
{
    CHECK_CLOSED(self);
    CHECK_EXPORTS(self);

    if (size < 0) {
        PyErr_Format(PyExc_ValueError,    
                     "negative size value %zd", size);
        return NULL;
    }

    if (size < self->string_size) {    
        self->string_size = size;    
        if (resize_buffer(self, size) < 0)    
            return NULL;   
    }

    return PyLong_FromSsize_t(size);

}

тест if (size < self->string_size) гарантирует, что ничего не будет сделано, если размер больше предыдущего размера.

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

Требуемое поведение можно эмулировать довольно просто, написав в конце объекта, если мы знаем, что он потерпит неудачу:

def my_truncate(data,size):
    current_size = len(data.getvalue())
    if size < current_size:
        return data.truncate(size)
    elif size == current_size:
        return size  # optim
    else:
        # store current position
        old_pos = data.tell()
        # go to end
        data.seek(current_size)
        # write zeroes
        data.write(b"\x00" * (size-current_size))
        # restore previous file position
        data.seek(old_pos)
        return size
person Jean-François Fabre    schedule 17.12.2019
comment
Спасибо. Кажется, я не первый, кто столкнулся с этой ошибкой (?) bugs.python.org/issue27261. - person Deedee Megadoodoo; 17.12.2019