memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples
NumPy arrays and memoryview are a perfect match in 2026 — both support the buffer protocol, allowing you to create zero-copy, high-performance views into large numerical arrays without duplicating memory. This is especially powerful for machine learning preprocessing, image/video handling, scientific simulations, and any workflow involving gigabyte-scale arrays where copying would kill performance or exceed RAM.
I’ve used memoryview + NumPy extensively in computer vision pipelines, time-series feature extraction, and large-scale data augmentation — slicing 4 GB image batches in microseconds without extra allocations. This March 2026 guide dives deep into how they integrate, real examples (image crops, ML batch views, shared memory patterns), performance comparisons, and best practices for modern Python (3.12–3.14+).
TL;DR — Key Takeaways 2026
- memoryview(np_array) creates a zero-copy view — slicing doesn’t copy data
- Advantages: huge memory savings + speed on large arrays (GB-scale images, tensors)
- Main use cases: image cropping without allocation, ML batch slicing, interop with C/C++ extensions, shared memory between processes
- NumPy view vs memoryview: NumPy .view() is usually better inside NumPy code; memoryview shines for raw buffer access or external libs
- 2026 tip: Use with free-threading (Python 3.14+) for concurrent array views
1. Why memoryview + NumPy Matters in 2026
NumPy arrays are already memory-efficient, but slicing large arrays with normal Python slicing (on .tobytes() or buffer) creates copies. memoryview avoids this entirely — you get a lightweight, sliceable window into the array’s raw memory buffer.
Real 2026 benefits:
- Process 10 GB image datasets without OOM
- Feed sub-regions directly to ML models (zero-copy batching)
- Interop with C extensions, OpenCV, PyTorch (via buffer protocol)
- Shared memory patterns in multiprocessing
2. Basic Integration — Creating & Slicing memoryview from NumPy
import numpy as np
# Create large array (simulate 1000×1000 grayscale image)
arr = np.random.randint(0, 256, (1000, 1000), dtype=np.uint8)
# Create zero-copy memoryview
mv = memoryview(arr)
print(mv.shape) # (1000, 1000) — preserves ndarray shape
print(mv.strides) # (1000, 1) — memory layout info
print(mv.itemsize) # 1 (uint8)
# Zero-copy 2D slicing (no data copied!)
crop = mv[200:800, 300:700]
print(crop.shape) # (600, 400)
print(len(crop.tobytes())) # 600 * 400 = 240000 bytes — but no new array allocation
Important: memoryview preserves NumPy’s multi-dimensional shape and strides — unlike bytes slicing which flattens.
3. Real-World Examples 2026
Example 1: Zero-Copy Image Cropping for ML Preprocessing
# Simulate batch of images (batch, height, width, channels)
images = np.random.randint(0, 256, (32, 512, 512, 3), dtype=np.uint8)
mv_batch = memoryview(images)
# Extract center 256×256 crop from each image — zero copy
center_crops = mv_batch[:, 128:384, 128:384, :]
# Feed directly to model (e.g. torch.frombuffer or tf.data)
print(center_crops.shape) # (32, 256, 256, 3) — view, not copy
Example 2: Shared Memory with multiprocessing
from multiprocessing import shared_memory, Process
import numpy as np
def worker(shm_name, shape):
shm = shared_memory.SharedMemory(name=shm_name)
arr = np.ndarray(shape, dtype=np.float32, buffer=shm.buf)
mv = memoryview(arr)
# Work on sub-view without copying
sub = mv[::2] # every other element
print("Worker modified sub-view")
# Main
shape = (10000000,)
arr = np.random.rand(*shape).astype(np.float32)
shm = shared_memory.SharedMemory(create=True, size=arr.nbytes)
arr_shared = np.ndarray(shape, dtype=arr.dtype, buffer=shm.buf)
arr_shared[:] = arr
p = Process(target=worker, args=(shm.name, shape))
p.start()
p.join()
shm.close()
shm.unlink()
Example 3: Interop with C extensions / external libs
Many C libs (via ctypes, cffi) expect raw buffers. memoryview gives you .tobytes() or direct buffer access without copying the NumPy array.
4. memoryview vs NumPy .view() vs .copy() – Comparison 2026
| Method | Zero-Copy? | Multi-dim shape preserved? | Best For | Performance on 1 GB array slice |
|---|---|---|---|---|
| arr[slice] | Yes (NumPy view) | Yes | Inside NumPy code | Extremely fast |
| memoryview(arr)[slice] | Yes | Yes | Interop with non-NumPy code | Very fast (similar) |
| arr.tobytes()[slice] | No (copies whole array first) | No (flat) | Never for large arrays | Slow + high RAM |
| arr.copy()[slice] | No | Yes | When you need independent copy | Slow + high RAM |
Rule of thumb 2026: Stay in NumPy → use .view() or slicing. Need raw buffer / external lib interop → memoryview(arr).
5. Performance & Best Practices 2026
- Prefer NumPy slicing inside pure NumPy code — it's optimized and keeps dtype/shape
- Use memoryview when passing to C extensions, mmap, sockets, or non-NumPy consumers
- Avoid: memoryview on very small arrays (overhead)
- Combine with: torch.frombuffer(), cv2.frombuffer(), multiprocessing shared_memory
- Free-threading (3.14+): concurrent memoryview access is safer/faster
- Profile: Use scalene or py-spy — memoryview eliminates allocation bottlenecks
Conclusion — When to Use memoryview with NumPy in 2026
Use NumPy slicing / .view() for most array work — it's the idiomatic, fastest way. Switch to memoryview when you need raw buffer protocol access, zero-copy interop with external libraries, or shared memory patterns in multiprocessing. In large-scale ML, image processing, or scientific code, this combo can save gigabytes of RAM and hours of runtime.
Next steps:
- Try memoryview on a large NumPy array slice today
- Related articles: memoryview() Zero-Copy Guide • Efficient Python Code 2026