본문 바로가기
C.W.K.
Stream
Lesson 01 of 07 · published

NumPy 배열 vs Python 리스트 — ndarray 가 왜?

~13 min · numpy, ndarray, vectorization

Level 0구경꾼
0 XP0/47 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

모든 것의 토대

NumPy (Numerical Python) 는 Python 수치 연산 거의 전부의 토대야. Pandas 가 이 위에 서 있고, PyTorch 와 TensorFlow 도 이 배열 언어를 공용어로 쓰고, scikit-learn 은 아예 요구해. PyArrow 도 대부분의 자리에서 호환되는 배열을 건네줘. ndarray 를 이해하면 modern Python data stack 의 엔진룸을 이해한 거야.

지금 stable 은 NumPy 2.5.1 (2026.7) 이야. NumPy 2.0 (2024.6) 이 legacy API 를 정리한 breaking-change 릴리스였고, 2.0 이후가 modern 세상이야.

왜 빨라?

Python 리스트는 포인터 벡터야. 요소 하나하나가 자기 type tag 와 reference count 와 allocation 을 가진 완전한 Python 객체고. 백만 요소 리스트를 순회하면 포인터 dereference 백만 번에 Python 메서드 dispatch 백만 번이 따라와.

NumPy ndarray 는 다르게 생겼어 — 같은 타입의 값들이 이어진 메모리 한 덩어리에 그대로 누워 있어. 요소별 포인터도, 요소별 type tag 도 없어. 순회는 Python overhead 없는 촘촘한 C loop 야. 리스트에선 몇 초 걸리던 연산이 배열에선 밀리초로 끝나.

그래서 손에 쥐는 게 뭐야?

  • 빠른 n차원 배열 — 같은 타입, contiguous memory.
  • Vectorized 연산 — 요소별 math 가 Python loop 없이 C 속도로.
  • Broadcasting — 모양이 다른 배열들이 알아서 줄을 맞춰.
  • Universal functions (ufuncs)np.sin, np.exp, np.maximum 같은 것들, 전부 element-wise 에 병렬화 가능.
  • Linear algebra, FFT, random — 수치 연장통이 기본으로 들어 있어.
  • Modern Generator APInp.random.default_rng() 가 legacy global state 를 대체.

Code

리스트 vs ndarray — 속도 차이는 미묘하지 않아·python
import numpy as np, time, math

n = 5_000_000
py_list = list(range(n))
np_arr  = np.arange(n)

# Pure Python — 요소별 sqrt
t = time.perf_counter()
py_result = [math.sqrt(x) for x in py_list]
print(f'list comprehension: {time.perf_counter() - t:.3f}s')

# NumPy — 요소별 sqrt, vectorized
t = time.perf_counter()
np_result = np.sqrt(np_arr)
print(f'np.sqrt:           {time.perf_counter() - t:.3f}s')

# 최근 노트북에서 ~50–100x. 배열 클수록 격차 더 벌어짐.
Modern random Generator API — reproducibility 내장·python
import numpy as np

rng = np.random.default_rng(seed=42)        # modern 진입점
data = rng.normal(loc=100, scale=15, size=1_000_000)

# Vectorized 통계, loop 없음
print(f'mean: {data.mean():.2f}')           # ~100.0
print(f'std:  {data.std():.2f}')            # ~15.0

# Boolean masking — 역시 vectorized
above_120 = data[data > 120]
print(f'fraction > 120: {len(above_120) / len(data):.3%}')

# Old style (legacy global state) — 작동은 하지만 새 코드에선 쓰지 마
# np.random.seed(42); np.random.normal(...)

External links

Exercise

rng.normal() 로 정규분포 값 백만 개를 만들어. 평균을 두 방법으로 계산해 봐: Python for 루프 + sum() / len(), 그리고 arr.mean(). time.perf_counter() 로 둘 다 재고 배율을 적어 둬. 앞으로 데이터 일을 하는 내내 그 배율 위에서 살게 될 거야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.