NumPyMiddleTechnical

В чём разница между ravel() и flatten() — когда каждый из них возвращает view, а когда копию?

flatten() всегда возвращает копию. ravel() возвращает view если массив C-contiguous, иначе копию. Для производительности предпочтительнее ravel(), когда не нужна независимая копия.

ravel() vs flatten(): семантика и производительность

Обе функции преобразуют многомерный массив в одномерный, но отличаются по тому, возвращают ли они view (вид на те же данные) или copy (независимую копию).

flatten() — всегда копия

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6]])

flat = arr.flatten()  # Всегда возвращает новый массив
print(flat)           # [1 2 3 4 5 6]

# Изменение flat НЕ влияет на оригинал
flat[0] = 99
print(arr[0, 0])  # 1 — оригинал не изменился
print(np.shares_memory(arr, flat))  # False

# flatten поддерживает параметр order
print(arr.flatten(order='C'))  # [1 2 3 4 5 6] — строка за строкой
print(arr.flatten(order='F'))  # [1 4 2 5 3 6] — столбец за столбцом
print(arr.flatten(order='K'))  # Порядок как в памяти

ravel() — view если возможно, иначе копия

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6]])  # C-contiguous

raveled = arr.ravel()  # Возвращает VIEW (массив C-contiguous)
print(np.shares_memory(arr, raveled))  # True — те же данные!

# Изменение raveled МЕНЯЕТ оригинал
raveled[0] = 99
print(arr[0, 0])  # 99 — оригинал изменился!

# Для F-contiguous — ravel возвращает копию
f_arr = np.asfortranarray(arr)
f_raveled = f_arr.ravel()  # Копия (C-order нельзя выразить как view F-order)
print(np.shares_memory(f_arr, f_raveled))  # False

Когда ravel() возвращает view, а когда копию

import numpy as np

def check_view_or_copy(original, result):
    if np.shares_memory(original, result):
        return "VIEW"
    else:
        return "COPY"

# C-contiguous массив -> view
c_arr = np.zeros((4, 4))
print(check_view_or_copy(c_arr, c_arr.ravel()))  # VIEW

# F-contiguous массив -> копия (при order='C' по умолчанию)
f_arr = np.asfortranarray(np.zeros((4, 4)))
print(check_view_or_copy(f_arr, f_arr.ravel()))  # COPY
print(check_view_or_copy(f_arr, f_arr.ravel(order='F')))  # VIEW

# Транспонированный массив -> копия
t_arr = np.zeros((4, 4)).T  # F-contiguous после транспонирования
print(check_view_or_copy(t_arr, t_arr.ravel()))  # COPY

# Non-contiguous (срез с шагом) -> копия
s_arr = np.zeros((10, 10))[::2, ::2]
print(check_view_or_copy(s_arr, s_arr.ravel()))  # COPY

Производительность: измерение разницы

import numpy as np
import time

N = 10_000_000
large = np.random.rand(N).reshape(1000, 10000)  # C-contiguous

# ravel: zero-cost для C-contiguous
start = time.perf_counter()
for _ in range(1000):
    _ = large.ravel()
print(f"ravel (view): {time.perf_counter() - start:.4f}s")

# flatten: всегда выделяет память
start = time.perf_counter()
for _ in range(1000):
    _ = large.flatten()
print(f"flatten (copy): {time.perf_counter() - start:.4f}s")
# flatten значительно медленнее из-за выделения памяти

Практические рекомендации

import numpy as np

arr = np.array([[1, 2], [3, 4]])

# Используйте ravel() когда нужна только одномерная форма без мутации
# (pipeline, передача в функцию без side effects)
for val in arr.ravel():  # Эффективно — view, нет копирования
    print(val)

# Используйте flatten() когда нужна независимая одномерная копия
copy = arr.flatten()
copy[0] = 999  # Не испортит оригинал arr

# Альтернатива ravel: arr.reshape(-1)
reshaped = arr.reshape(-1)  # Тоже view для C-contiguous
print(np.shares_memory(arr, reshaped))  # True

Подводные камни

  • Неожиданная мутация через ravel(): изменение элементов результата ravel() может изменить оригинальный массив если он C-contiguous. Используйте flatten() когда нужна безопасная копия.
  • ravel() не гарантирует view: поведение зависит от contiguity исходного массива. Нельзя полагаться на то, что ravel всегда возвращает view.
  • Порядок обхода (order): по умолчанию оба используют 'C' (row-major). Для F-contiguous данных рассмотрите ravel(order='F').
  • reshape(-1) vs ravel(): оба возвращают view для C-contiguous, но reshape(-1) вызывает ошибку для non-contiguous данных, а ravel() тихо делает копию.
  • np.ndarray.flat: атрибут .flat возвращает итератор flatiter — всегда работает с оригинальными данными без копирования, подходит для пробега по элементам.

Common mistakes

  • Объяснять ravel vs flatten только синтаксисом без shape, dtype, состояния или режима выполнения.
  • Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
  • Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.

What the interviewer is testing

  • Может ли связать ravel vs flatten с реальным контрактом входов и выходов.
  • Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
  • Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.

Sources

Related topics