Подтвердить что ты не робот

Что такое точка памяти в Python

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

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

класс memoryview (obj)

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

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

>>> 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 для отображения внутренних данных объекта буфера без копирование, однако, для того, чтобы делать что-либо полезное с объектом (путем вызова методов предоставляемый объектом), мы должны создать копию!

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

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

Edit1:

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

Люди предлагают обходные пути, например, многие функции строк и регулярных выражений занимают позицию аргументы, которые могут использоваться для подражания продвижению указателя. В этом есть две проблемы: первая это работа, вы вынуждены изменить свой стиль кодирования, чтобы преодолеть недостатки, и second: не все функции имеют аргументы позиции, например, функции регулярных выражений и startswith do, encode()/decode() do not.

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

Edit2:

Образец кода упростит ситуацию. Это то, что я хочу сделать, и то, что я предположил, будет иметь вид памяти, позволит мне сделать на первый взгляд. Позволяет использовать 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
4b9b3361

Ответ 1

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

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

import time
for n in (100000, 200000, 300000, 400000):
    data = 'x'*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
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print 'memoryview', n, time.time()-start

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

bytes 100000 0.200068950653
bytes 200000 0.938908100128
bytes 300000 2.30898690224
bytes 400000 4.27718806267
memoryview 100000 0.0100269317627
memoryview 200000 0.0208270549774
memoryview 300000 0.0303030014038
memoryview 400000 0.0403470993042

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

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

Ответ 2

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

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

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

Ответ 3

Вот код 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))

Ответ 4

Отличный пример сурьмы. На самом деле, в 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)

Ответ 5

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

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

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]. Таким образом, есть основания ожидать, что нам нужно получить требуемый_слайс_вью из обзора памяти и, следовательно, первым шагом является получение обзора памяти всего базового массива.

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

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

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

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