В чем именно заключается смысл memoryview в Python

Проверка документации по memoryview:

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

класс memoryview (obj)

Создайте представление памяти, которое ссылается на obj. obj должен поддерживать протокол буфера. Встроенные объекты, поддерживающие протокол буфера, включают байты и массив байтов.

Затем нам дается пример кода:

>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b'bce'

Цитата окончена, теперь давайте подробнее рассмотрим:

>>> b = b'long bytes stream'
>>> b.startswith(b'long')
True
>>> v = memoryview(b)
>>> vsub = v[5:]
>>> vsub.startswith(b'bytes')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'memoryview' object has no attribute 'startswith'
>>> bytes(vsub).startswith(b'bytes')
True
>>> 

Итак, что я понял из вышесказанного:

Мы создаем объект memoryview, чтобы раскрыть внутренние данные объекта буфера без копирования, однако, чтобы сделать что-нибудь полезное с объектом (путем вызова методов, предоставленных объектом), мы должны создать копию!

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

С приведенной выше схемой я не понимаю, как она может быть полезна в любой ситуации, если только кто-нибудь не объяснит мне, что мне здесь не хватает.

Редактировать1:

У нас есть большой кусок данных, мы хотим обработать его, продвигаясь по нему от начала до конца, например, извлекая токены из начала строкового буфера до тех пор, пока буфер не будет использован. В термине C это продвигает указатель через buffer, и указатель может быть передан любой функции, ожидающей типа буфера. Как можно сделать что-то подобное в Python?

Люди предлагают обходные пути, например, многие строковые и регулярные функции принимают аргументы позиции, которые можно использовать для имитации продвижения указателя. С этим есть две проблемы: во-первых, это обходной путь, вы вынуждены изменить свой стиль кодирования, чтобы преодолеть недостатки, и во-вторых: не все функции имеют аргументы позиции, например функции регулярных выражений и startswith do, _4 _ / _ 5_ don ' т.

Другие могут предложить загружать данные частями или обрабатывать буфер небольшими сегментами, размер которых превышает максимальный токен. Итак, мы знаем об этих возможных обходных путях, но мы должны работать в python более естественным образом, не пытаясь изменить стиль кодирования, чтобы он соответствовал языку - не так ли?

Edit2:

Образец кода проясняет ситуацию. Это то, что я хочу сделать, и то, что, как я предполагал, с первого взгляда позволит мне сделать memoryview. Давайте использовать pmview (правильное представление памяти) для функциональности, которую я ищу:

tokens = []
xlarge_str = get_string()
xlarge_str_view =  pmview(xlarge_str)

while True:
    token =  get_token(xlarge_str_view)
    if token: 
        xlarge_str_view = xlarge_str_view.vslice(len(token)) 
        # vslice: view slice: default stop paramter at end of buffer
        tokens.append(token)
    else:   
        break

person Basel Shishani    schedule 06.09.2013    source источник
comment
возможный дубликат Когда следует использовать memoryview?   -  person zr.    schedule 16.02.2014
comment
Ответ на указанный вопрос не содержит подробностей. Вопрос также не касается потенциальных проблем с точки зрения учащегося.   -  person Basel Shishani    schedule 26.02.2014


Ответы (5)


Одна из причин, по которой memoryviews полезны, заключается в том, что их можно разрезать без копирования базовых данных, в отличие от _2 _ / _ 3_.

Например, возьмем следующий игрушечный пример.

import time
for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = data
    while b:
        b = b[1:]
    print(f'     bytes {n} {time.time() - start:0.3f}')

for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print(f'memoryview {n} {time.time() - start:0.3f}')

На моем компьютере я получаю

     bytes 100000 0.211
     bytes 200000 0.826
     bytes 300000 1.953
     bytes 400000 3.514
memoryview 100000 0.021
memoryview 200000 0.052
memoryview 300000 0.043
memoryview 400000 0.077

Вы можете ясно видеть квадратичную сложность повторяющейся нарезки строки. Даже после 400000 итераций это уже неуправляемо. Между тем, версия memoryview имеет линейную сложность и молниеносно.

Изменить: Обратите внимание, что это было сделано в CPython. В Pypy до 4.0.1 была ошибка, вызывающая просмотры памяти чтобы иметь квадратичную производительность.

person Antimony    schedule 13.12.2015
comment
Этот ответ не касается того факта, что, как заявляет спрашивающий, чтобы сделать что-нибудь полезное, вам нужно использовать bytes (), который копирует объект ... - person ragardner; 15.03.2019
comment
@ Citizen2077 Как показывает мой пример, он полезен для эффективного выполнения промежуточных манипуляций, даже если вы в конечном итоге скопируете его в объект байтов. - person Antimony; 16.03.2019

memoryview объекты отлично подходят, когда вам нужны подмножества двоичных данных, которые должны только поддерживать индексацию. Вместо того, чтобы делать срезы (и создавать новые, потенциально большие) объекты для передачи другому API, вы можете просто взять объект memoryview.

Одним из таких примеров API может быть модуль struct. Вместо того, чтобы передавать фрагмент большого bytes объекта для анализа упакованных значений C, вы передаете memoryview только ту область, из которой необходимо извлечь значения.

memoryview объекты фактически поддерживают struct распаковку; вы можете выбрать область нижележащего объекта bytes с помощью среза, а затем использовать .cast() для «интерпретации» нижележащих байтов как длинных целых чисел, значений с плавающей запятой или n-мерных списков целых чисел. Это обеспечивает очень эффективную интерпретацию формата двоичного файла без необходимости создания дополнительных копий байтов.

person Martijn Pieters    schedule 06.09.2013
comment
А что делать, когда вам нужны подмножества, которые поддерживают не только индексирование ?! - person Basel Shishani; 06.09.2013
comment
@BaselShishani: не используйте memoryview. Тогда вы имеете дело с текстом, а не с двоичными данными. - person Martijn Pieters; 06.09.2013
comment
Да, дело с текстом. Итак, мы не используем memoryview, есть ли альтернатива? - person Basel Shishani; 06.09.2013
comment
Какую проблему ты пытаешься решить? Являются ли подстроки, которые вам нужны для тестирования, такими большими? - person Martijn Pieters; 06.09.2013
comment
@ Martijn: Еще один вопрос к вашему ответу: «вы передаете в памяти только ту область, из которой вам нужно извлечь значения»: Как вы передаете в память только область? Я вижу, что memoryview принимает только полный аргумент объекта. - person Basel Shishani; 07.09.2013
comment
@BaselShishani: нарезка memoryview возвращает новый вид памяти, охватывающий только эту область. - person Martijn Pieters; 07.09.2013
comment
@BaselShishani: вы можете использовать io.StringIO: docs.python.org/ 3 / library / io.html # io.StringIO - person Marco Sulla; 29.02.2016
comment
@MartijnPieters Значит, вы говорите, что приведение элементов по одному к объекту memoryview, полученному, например, из bytes, быстрее, чем разрезание самого bytes или преобразование всего memoryview среза в bytes? У тебя есть скамейка? - person Marco Sulla; 12.01.2020
comment
@MarcoSulla: представление памяти - это буквально взгляд на кусок памяти. Преобразование в байты производит копию, поэтому необходимо зарезервировать другую область памяти, и все будет скопировано. "приведение" происходит только постольку, поскольку значения обертываются в объект Python для представления типа; вам придется сделать это, если для значения bytes тоже (например, при индексации объекта bytes необходимо создать объект Python int для каждого значения). Отказ от копирования данных позволяет сэкономить время. - person Martijn Pieters; 13.01.2020
comment
@MarcoSulla: и в ответе Antimony уже есть тест, поэтому я отсылаю вас к нему. - person Martijn Pieters; 13.01.2020
comment
Я прочитал ответ «Сурьмы». Он выполняет только нарезку, а не cast(). Нарезка bytes не идентична нарезке memoryview, вы должны cast(), как вы сказали. - person Marco Sulla; 13.01.2020
comment
@MarcoSulla: под cast() вы имеете в виду memoryview.cast()? Это просто создает новый объект представления в той же области памяти с разными параметрами для обработки значений, если вы индексируете значения (в этот момент создается упакованное значение) или назначаете индекс (распаковка объекта Python в байты). Это точно такая же операция, как при создании struct.Struct() объекта, нет происходят фактические преобразования. - person Martijn Pieters; 13.01.2020
comment
@MarcoSulla: например, memoryviewobj.cast("Q") эквивалентно использованию объекта struct.Struct("Q"), а затем использованию .pack(intvalue) или .unpack(bytesvalue), преобразования между int и bytes. За исключением того, что нет необходимости сначала создавать bytes копию memoryview. - person Martijn Pieters; 13.01.2020

Позвольте мне объяснить, в чем заключается проблема понимания.

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

desired_slice_view = memoryview(existing_array, start_index, end_index)

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

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

whole_view = memoryview(existing_array)
desired_slice_view = whole_view[10:20]

Короче говоря, цель первой строки - просто предоставить объект, реализация среза которого (dunder-getitem) возвращает представление в памяти.

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

  1. Наш желаемый результат - это представление в памяти, представляющее собой кусок чего-то. Обычно мы получаем срезанный объект из объекта того же типа, используя для него оператор среза [10:20]. Так что есть основания ожидать, что нам нужно получить желаемый_slice_view из memoryview, и, следовательно, первым шагом является получение memoryview всего базового массива.

  2. Наивное ожидание конструктора memoryview с аргументами start и end не учитывает, что спецификация среза действительно требует всей выразительности обычного оператора среза (включая такие вещи, как [3 :: 2] или [: -4] и т. Д.). Невозможно просто использовать существующий (и понятный) оператор в этом однострочном конструкторе. Вы не можете присоединить его к аргументу existing_array, так как это сделает часть этого массива вместо того, чтобы сообщать конструктору memoryview некоторые параметры среза. И вы не можете использовать сам оператор в качестве аргумента, потому что это оператор, а не значение или объект.

Возможно, конструктор memoryview мог бы взять объект среза:

desired_slice_view = memoryview(existing_array, slice(1, 5, 2) )

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

person gwideman    schedule 11.02.2019

Вот код python3.

#!/usr/bin/env python3

import time
for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = data
    while b:
        b = b[1:]
    print ('bytes {:d} {:f}'.format(n,time.time()-start))

for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print ('memview {:d} {:f}'.format(n,time.time()-start))
person jimaf    schedule 24.07.2018

Отличный пример от «Сурьмы». Фактически, в Python3 вы можете заменить data = 'x' * n на data = bytes (n) и поместить скобки в операторы печати, как показано ниже:

import time
for n in (100000, 200000, 300000, 400000):
    #data = 'x'*n
    data = bytes(n)
    start = time.time()
    b = data
    while b:
        b = b[1:]
    print('bytes', n, time.time()-start)

for n in (100000, 200000, 300000, 400000):
    #data = 'x'*n
    data = bytes(n)
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print('memoryview', n, time.time()-start)
person user2494386    schedule 28.02.2019