Die Python-Funktion memoryview(obj) erzeugt einen Zero-Copy-View auf ein Buffer-Objekt — bytes, bytearray, array.array oder numpy-Arrays. Slicing eines memoryview erzeugt KEINE Kopie der Daten, sondern nur einen neuen View. In Performance-kritischen I/O- und Binär-Daten-Operationen kann das gewaltige Speicher- und CPU-Ersparnis bedeuten.
Einleitung
Wer in Python mit bytes arbeitet und immer wieder neue Slices erzeugt, kopiert ständig Daten. Bei Multi-MB-Buffern wird das schnell teuer. memoryview umgeht das, indem es auf den vorhandenen Speicher zeigt, ohne ihn zu kopieren.
Anwendungsfälle:
- Streaming-I/O: Daten aus einer Datei oder einem Socket in einen großen Buffer lesen und Sub-Ranges per
memoryviewan Verarbeitungsfunktionen weiterreichen. - Binär-Protokolle: Header-Bytes lesen, Payload als View weitergeben — ohne Kopie.
- NumPy-Interop:
memoryviewist die universelle „Lingua Franca" zwischen Buffer-Konsumenten.
Mit cast() lässt sich der View typisiert umschalten — von Bytes auf Floats, Doubles, Ints etc.
Syntax
memoryview(buffer_object)buffer_object Ein Objekt, das das Buffer-Protokoll unterstützt: bytes, bytearray, array.array, mmap.mmap, numpy.ndarray.
Rückgabewert
Eine memoryview-Instanz mit Attributen format, itemsize, nbytes, ndim, shape, strides, readonly.
Beispiele
Zero-Copy-View
Der grundlegende Effekt: Slicing erzeugt keine Kopie der Daten, sondern nur einen anderen View:
data = bytearray(b"abcdef")
view = memoryview(data)
sub = view[1:4]
print(bytes(sub))
# Modifikation des Original wirkt auch in den Views:
data[1] = ord('B')
print(bytes(sub))b'bcd'
b'Bcd'View ist read/write je nach Quelle
Bei bytes ist der View read-only, bei bytearray schreibbar:
v_ro = memoryview(b"hello")
print(v_ro.readonly)
v_rw = memoryview(bytearray(b"hello"))
print(v_rw.readonly)
v_rw[0] = ord('H')
print(bytes(v_rw))True
False
b'Hello'cast() — typisierten View bauen
Mit cast() lassen sich Bytes als Sequenz von Ints, Floats oder Doubles betrachten — ohne die Daten zu kopieren:
import struct
# Drei Doubles als Bytes packen:
raw = struct.pack("3d", 1.5, 2.5, 3.5)
view = memoryview(raw).cast("d")
print(list(view))[1.5, 2.5, 3.5]Praktische Beispiele
Großen Datei-Buffer streamen ohne Kopie
readinto() füllt einen vorab allokierten Buffer — memoryview reicht Sub-Ranges weiter, ohne neue Bytes-Kopien zu produzieren:
BUF_SIZE = 4096
buf = bytearray(BUF_SIZE)
view = memoryview(buf)
with open("big.bin", "rb") as f:
while True:
n = f.readinto(buf)
if not n:
break
process(view[:n]) # zero-copyHeader und Payload trennen
Beim Parsen binärer Protokolle (TCP, Image-Formate) lässt sich der Header lesen und die Payload als View weitergeben:
packet = bytearray(b"\x00\x10HELLO PAYLOAD")
view = memoryview(packet)
header = view[:2] # zwei Header-Bytes
payload = view[2:] # Rest als View
print(int.from_bytes(header, "big"), bytes(payload))16 b'HELLO PAYLOAD'Performance-Vergleich
Bei großen Buffern macht memoryview einen massiven Unterschied — Slicing produziert sonst Kopien:
from time import perf_counter
big = bytes(10_000_000)
# Mit Kopien:
t = perf_counter()
for _ in range(100):
sub = big[1000:2000]
copy_time = perf_counter() - t
# Mit memoryview:
t = perf_counter()
view = memoryview(big)
for _ in range(100):
sub = view[1000:2000]
view_time = perf_counter() - t
print(f"copies: {copy_time*1000:.2f} ms, views: {view_time*1000:.2f} ms")copies: ... ms, views: ... ms (deutlich schneller)Praktische Hinweise
- Zero-Copy-Slicing ist der Hauptvorteil — wer nicht-trivialen Slice-Workload hat, gewinnt enorm.
- Lebenszeit beachten: Solange ein
memoryviewexistiert, kann das Original nicht resized werden (bytearray.appendschlägt fehl). cast()für typisierte Views — Buffer als Floats, Doubles, Ints betrachten.release()schließt einen View explizit — wichtig bei langlebigen Buffern.- Verwandte Typen:
bytes,bytearray,array.array,mmap,numpy.ndarray.