Skip to main content

리스트 (List)

학습 목표

  • 리스트의 가변(Mutable) 특성과 순서(Ordered) 특성을 이해한다
  • 인덱싱과 슬라이싱으로 리스트 요소에 접근할 수 있다
  • CRUD(생성, 읽기, 수정, 삭제) 메서드를 활용할 수 있다
  • 중첩 리스트를 다룰 수 있다

왜 중요한가

리스트는 Python에서 가장 많이 사용하는 자료구조입니다. 데이터의 모음을 저장하고, 순서대로 처리하고, 동적으로 크기를 조절할 수 있습니다. ML/DL에서 학습 데이터, 예측 결과, 손실값 이력 등 거의 모든 데이터 컬렉션이 리스트(또는 그 확장인 NumPy 배열)로 시작합니다.

리스트 생성

# 리터럴 생성
fruits = ["사과", "바나나", "체리"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, None]  # 다양한 타입 혼합 가능
empty = []

# list() 생성자
from_range = list(range(5))          # [0, 1, 2, 3, 4]
from_string = list("Python")        # ['P', 'y', 't', 'h', 'o', 'n']
from_tuple = list((1, 2, 3))        # [1, 2, 3]

# 반복으로 생성
zeros = [0] * 5                     # [0, 0, 0, 0, 0]
pattern = [1, 2] * 3                # [1, 2, 1, 2, 1, 2]

인덱싱과 슬라이싱

문자열과 동일한 방식으로 동작합니다.
colors = ["빨강", "주황", "노랑", "초록", "파랑"]

# 인덱싱
print(colors[0])     # "빨강"
print(colors[-1])    # "파랑"
print(colors[2])     # "노랑"

# 슬라이싱
print(colors[1:3])   # ["주황", "노랑"]
print(colors[:3])    # ["빨강", "주황", "노랑"]
print(colors[2:])    # ["노랑", "초록", "파랑"]
print(colors[::2])   # ["빨강", "노랑", "파랑"]
print(colors[::-1])  # ["파랑", "초록", "노랑", "주황", "빨강"]

# 슬라이싱으로 새 리스트 생성 (얕은 복사)
copy = colors[:]
print(copy is colors)  # False (다른 객체)

CRUD 연산

Create (생성/추가)

fruits = ["사과", "바나나"]

# 끝에 추가
fruits.append("체리")
print(fruits)  # ["사과", "바나나", "체리"]

# 특정 위치에 삽입
fruits.insert(1, "포도")
print(fruits)  # ["사과", "포도", "바나나", "체리"]

# 여러 요소 추가
fruits.extend(["키위", "망고"])
print(fruits)  # ["사과", "포도", "바나나", "체리", "키위", "망고"]

# + 연산자 (새 리스트 생성)
combined = fruits + ["딸기", "수박"]
append()extend()의 차이에 주의하세요.
a = [1, 2, 3]
a.append([4, 5])     # [1, 2, 3, [4, 5]]  <- 리스트가 요소로 추가

b = [1, 2, 3]
b.extend([4, 5])     # [1, 2, 3, 4, 5]    <- 요소들이 펼쳐서 추가

Read (읽기/검색)

numbers = [10, 20, 30, 20, 40]

# 요소 존재 확인
print(20 in numbers)          # True
print(50 in numbers)          # False

# 위치 찾기
print(numbers.index(20))     # 1 (첫 번째 발견 위치)
print(numbers.index(20, 2))  # 3 (인덱스 2부터 검색)

# 개수 세기
print(numbers.count(20))     # 2

# 길이
print(len(numbers))          # 5

Update (수정)

colors = ["빨강", "주황", "노랑", "초록"]

# 단일 요소 수정
colors[0] = "분홍"
print(colors)  # ["분홍", "주황", "노랑", "초록"]

# 슬라이싱으로 여러 요소 수정
colors[1:3] = ["파랑", "보라"]
print(colors)  # ["분홍", "파랑", "보라", "초록"]

# 슬라이싱으로 요소 수 변경도 가능
colors[1:3] = ["A", "B", "C", "D"]
print(colors)  # ["분홍", "A", "B", "C", "D", "초록"]

Delete (삭제)

items = ["a", "b", "c", "d", "e"]

# 값으로 삭제 (첫 번째 발견만)
items.remove("c")
print(items)   # ["a", "b", "d", "e"]

# 인덱스로 삭제 (반환)
popped = items.pop()      # 마지막 요소 제거 및 반환
print(popped)              # "e"
print(items)               # ["a", "b", "d"]

popped = items.pop(1)      # 인덱스 1 제거 및 반환
print(popped)              # "b"

# del 키워드
del items[0]
print(items)   # ["d"]

# 전체 삭제
items.clear()
print(items)   # []

정렬

numbers = [3, 1, 4, 1, 5, 9, 2, 6]

# 원본을 변경하는 정렬 (in-place)
numbers.sort()
print(numbers)                # [1, 1, 2, 3, 4, 5, 6, 9]

numbers.sort(reverse=True)
print(numbers)                # [9, 6, 5, 4, 3, 2, 1, 1]

# 원본을 유지하는 정렬 (새 리스트 반환)
original = [3, 1, 4, 1, 5]
sorted_list = sorted(original)
print(original)               # [3, 1, 4, 1, 5] (변경 없음)
print(sorted_list)            # [1, 1, 3, 4, 5]

# 키 함수로 정렬
words = ["banana", "apple", "cherry"]
words.sort(key=len)           # 길이순 정렬
print(words)                  # ["apple", "banana", "cherry"]

students = [("김철수", 85), ("이영희", 92), ("박민수", 78)]
students.sort(key=lambda s: s[1], reverse=True)  # 점수 내림차순
print(students)  # [("이영희", 92), ("김철수", 85), ("박민수", 78)]

# 뒤집기
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers)                # [5, 4, 3, 2, 1]

중첩 리스트

# 2차원 리스트 (행렬)
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# 접근
print(matrix[0])       # [1, 2, 3] (첫 번째 행)
print(matrix[0][1])    # 2 (0행 1열)
print(matrix[2][2])    # 9 (2행 2열)

# 순회
for row in matrix:
    for value in row:
        print(value, end=" ")
    print()
# 1 2 3
# 4 5 6
# 7 8 9
중첩 리스트를 * 연산자로 생성할 때 주의하세요.
# 잘못된 방법 (같은 리스트를 참조)
wrong = [[0] * 3] * 3
wrong[0][0] = 1
print(wrong)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]  <- 모두 변경됨!

# 올바른 방법
correct = [[0] * 3 for _ in range(3)]
correct[0][0] = 1
print(correct)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]  <- 첫 행만 변경

유용한 내장 함수

numbers = [3, 1, 4, 1, 5, 9]

print(len(numbers))    # 6
print(sum(numbers))    # 23
print(min(numbers))    # 1
print(max(numbers))    # 9
print(any(numbers))    # True (하나라도 Truthy)
print(all(numbers))    # True (모두 Truthy)

AI/ML에서의 활용

# 손실값 이력 저장
loss_history = []
for epoch in range(5):
    loss = 1.0 / (epoch + 1)  # 예시 손실값
    loss_history.append(loss)
print(loss_history)  # [1.0, 0.5, 0.333..., 0.25, 0.2]

# 배치 데이터 분할
data = list(range(100))
batch_size = 32
batches = [data[i:i+batch_size] for i in range(0, len(data), batch_size)]
print(f"배치 수: {len(batches)}")  # 배치 수: 4

# NumPy 배열로 변환 (리스트 -> 텐서의 첫 단계)
import numpy as np
features = [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]
array = np.array(features)
print(array.shape)  # (3, 2)
Python 리스트는 다양한 타입을 담을 수 있지만, NumPy 배열은 동일한 타입만 허용합니다. 대신 배열은 벡터/행렬 연산이 C 수준으로 빠릅니다. ML에서는 리스트로 데이터를 수집한 후, NumPy 배열이나 PyTorch 텐서로 변환하는 패턴이 일반적입니다.
b = a는 같은 객체를 참조합니다. 독립적인 복사를 원하면 b = a[:], b = a.copy(), 또는 b = list(a)를 사용합니다. 중첩 리스트는 copy.deepcopy(a)가 필요합니다.

체크리스트

  • 리스트 생성, 인덱싱, 슬라이싱을 자유롭게 할 수 있다
  • append(), extend(), insert(), remove(), pop() 메서드를 구분하여 사용할 수 있다
  • sort()sorted()의 차이를 설명할 수 있다
  • 중첩 리스트의 참조 문제를 인지하고 올바르게 생성할 수 있다

다음 문서