Сообщить об ошибке.

Тип memoryview, буфер обмена

Новое в Python 3.14 Тип memoryview теперь поддерживает подписку (subscription), что делает его обобщённым (дженерик) типом.

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

В Python объекты memoryview обрабатываются встроенным классом memoryview(). Встроенный класс memoryview() имеет несколько своих методов.

Memoryview имеет понятие элемента, который является атомарной единицей памяти, обрабатываемой исходным объектом obj. Для многих простых типов, таких как bytes и bytearray, элемент является одним байтом, но другие типы, такие как array.array может иметь более крупные элементы.

Значение len(view) соответствует длине метода memoryview.tolist(), который возвращает вложенный список, представляющий данные из memoryview. Если view.ndim == 1, то это значение равно количеству элементов в представлении.

Изменено в Python 3.12: Теперь при view.ndim == 0 вызов len(view) вызывает исключение TypeError, вместо того чтобы возвращать число 1.

До Python 3.12:

>>> m = memoryview(array.array('i', []))
>>> m.ndim = 0
>>> len(m)
1

Это было нелогично, потому что объект с нулевой размерностью (ndim == 0) не должен иметь длину больше 0.

Начиная с Python 3.12 такая конструкция вызывает ошибку, потому что объекты с ndim == 0 не имеют определённой длины.

>>> m = memoryview(array.array('i', []))
>>> m.ndim = 0
>>> len(m)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: len() of unsized object

Атрибут memoryview.itemsize даст количество байт в одном элементе.

Memoryview поддерживает срезы и индексирование для предоставления своих данных. Одномерное нарезание приведет к подпредставлению:

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

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

Вот пример с небайтовым форматом:

>>> import array
>>> a = array.array('l', [-11111111, 22222222, -33333333, 44444444])
>>> m = memoryview(a)
>>> m[0]
# -11111111
>>> m[-1]
# 44444444
>>> m[::2].tolist()
# [-11111111, -33333333]

Если базовый объект доступен для записи, то memoryview поддерживает одномерное назначение среза. Изменение размера не допускается:

>>> data = bytearray(b'abcefg')
>>> v = memoryview(data)
>>> v.readonly
# False

>>> v[0] = ord(b'z')
>>> data
# bytearray(b'zbcefg')

>>> v[1:4] = b'123'
>>> data
# bytearray(b'z123fg')

>>> v[2:3] = b'spam'
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# ValueError: memoryview assignment: lvalue and rvalue have different structures

>>> v[2:6] = b'spam'
>>> data
# bytearray(b'z1spam')

Одномерное memoryviews хешируемых (только для чтения) типов с форматами 'B', 'b' или 'c' также можно хешировать. Хеш определяется как: hash(m) == hash(m.tobytes()):

>>> v = memoryview(b'abcefg')
>>> hash(v) == hash(b'abcefg')
# True
>>> hash(v[2:4]) == hash(b'ce')
# True
>>> hash(v[::-2]) == hash(b'abcefg'[::-2])
# True

Новое в Python 3.14

Тип memoryview теперь поддерживает подписку (subscription), что делает его обобщённым (дженерик) типом.

Что значит "поддерживает подписку"?

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

from typing import TypeVar

T = TypeVar('T')
class MyContainer(list[T]): ...

В случае memoryview, теперь можно сделать так:

from typing import Any

# Это говорит, что memoryview содержит целые числа
x: memoryview[int]

Это не влияет на выполнение кода, но помогает статическим анализаторам типов (например, mypy, pyright) лучше понимать, с какими данными ты работаешь.

Что значит "generic type"?

Обобщённый тип (или generic type) - это тип, который может принимать параметры типа, чтобы уточнить, какие данные он содержит.

Например:

list[int]      # список целых чисел
dict[str, int] # словарь из строк и целых чисел
set[float]     # множество чисел с плавающей точкой

Теперь такая возможность есть и у memoryview.

Пример:

from typing import Any

def process(data: memoryview[int]) -> None:
    for byte in data:
        print(byte)

data = memoryview(bytearray(b'Hello'))
process(data)  # Теперь тип проверяется явно

Обрати внимание: эта проверка работает только на уровне типизации. В рантайме memoryview всё равно не знает, какой тип вы указали через [].

Зачем это нужно?

  • Повышает читаемость кода.
  • Улучшает проверку типов с помощью инструментов вроде mypy.
  • Делает memoryview более согласованным с другими коллекциями, поддерживающими дженерики.