[Python] list: 가변 시퀀스
python list, 파이썬 리스트, 리스트 컴프리헨션, list comprehension
정의
Python의 list는 **가변 길이 동적 배열(dynamic array)**이다. C의 ArrayList/Java ArrayList와 같은 자료구조로, 내부적으로 PyObject 포인터의 연속 배열을 over-allocation 전략으로 관리한다. 임의 인덱스 접근 O(1), 끝 추가 amortized O(1), 임의 위치 삽입·삭제 O(n).
기본 사용
empty = []
nums = [1, 2, 3]
mixed = [1, "two", 3.0, [4, 5]] # 타입 혼합 가능
from_iter = list(range(5)) # [0, 1, 2, 3, 4]
인덱싱과 슬라이싱
python
xs = [10, 20, 30, 40, 50]
print(xs[0], xs[-1])
print(xs[1:4]) # [20, 30, 40]
print(xs[::2]) # [10, 30, 50]
print(xs[::-1]) # 뒤집기
xs[1:3] = [99, 99, 99] # 슬라이스 할당 (길이 변경 가능)
print(xs) 결과
10 50
[20, 30, 40]
[10, 30, 50]
[50, 40, 30, 20, 10]
[10, 99, 99, 99, 40, 50]가변 메서드
| 메서드 | 동작 | 복잡도 |
|---|---|---|
append(x) | 끝 추가 | amortized O(1) |
extend(iter) | 다른 iter 합치기 | O(k) |
insert(i, x) | i번째에 삽입 | O(n) |
pop([i]) | 끝(또는 i) 제거 후 반환 | O(1) / O(n) |
remove(x) | 첫 매치 제거 | O(n) |
clear() | 비우기 | O(n) |
reverse() | 제자리 뒤집기 | O(n) |
sort(key=, reverse=) | 제자리 정렬 (Timsort) | O(n log n) |
index(x[, start, end]) | 위치 (없으면 ValueError) | O(n) |
count(x) | 출현 횟수 | O(n) |
copy() | 얕은 복사 | O(n) |
python
xs = [3, 1, 4, 1, 5, 9, 2, 6]
xs.append(5)
xs.extend([5, 3, 5])
print(xs)
xs.sort()
print(xs)
xs.sort(reverse=True)
print(xs)
# key 정렬: 길이 기준
words = ["python", "hi", "world"]
words.sort(key=len)
print(words) 결과
[3, 1, 4, 1, 5, 9, 2, 6, 5, 5, 3, 5]
[1, 1, 2, 3, 3, 4, 5, 5, 5, 5, 6, 9]
[9, 6, 5, 5, 5, 5, 4, 3, 3, 2, 1, 1]
['hi', 'world', 'python']sorted() vs sort()
list.sort(): 제자리, 반환Nonesorted(iter): 새 리스트 반환, 모든 iterable 가능
nums = [3, 1, 4]
nums.sort() # nums 자체 정렬, None 반환
new = sorted(nums) # 새 리스트
new = sorted(nums, key=lambda x: -x)
리스트 컴프리헨션
가독성과 속도를 모두 잡는 Pythonic 패턴.
python
squares = [x ** 2 for x in range(5)]
evens = [x for x in range(10) if x % 2 == 0]
matrix = [[i * j for j in range(3)] for i in range(3)]
print(squares)
print(evens)
print(matrix) 결과
[0, 1, 4, 9, 16]
[0, 2, 4, 6, 8]
[[0, 0, 0], [0, 1, 2], [0, 2, 4]]성능 비교: 동일 결과를 for + append로 만드는 것보다 30% 이상 빠르다(전용 바이트코드 LIST_APPEND).
얕은 복사 vs 깊은 복사
import copy
a = [[1, 2], [3, 4]]
shallow = a.copy() # 또는 list(a), a[:]
shallow[0].append(99) # 내부 리스트는 공유
print(a) # [[1, 2, 99], [3, 4]] (영향 받음)
deep = copy.deepcopy(a)
deep[0].append(100)
print(a) # deep만 변경됨
중요: [[]] * 3은 같은 빈 리스트 3개 참조다. 함정.
python
wrong = [[]] * 3
wrong[0].append(1)
print(wrong) # 모두 같은 리스트
right = [[] for _ in range(3)]
right[0].append(1)
print(right) 결과
[[1], [1], [1]]
[[1], [], []]언패킹과 스타 표현식
a, b, c = [1, 2, 3]
first, *rest = [1, 2, 3, 4] # first=1, rest=[2, 3, 4]
first, *mid, last = [1, 2, 3, 4, 5] # mid=[2, 3, 4]
리스트 vs 다른 타입
| 상황 | 선택 |
|---|---|
| 변경 가능, 순서 중요 | list |
| 변경 불가, 순서 중요 | tuple |
| 빈번한 양끝 삽입/삭제 | collections.deque (양끝 O(1)) |
| 큰 수치 배열, 수학 연산 | numpy.ndarray |
| 중복 없음, 멤버십 체크 | set |
성능 함정
list.insert(0, x): O(n). 큐로 쓰면deque사용x in list: O(n). 멤버십 체크가 빈번하면set- 큰 정수 배열:
numpy.ndarray가 메모리 1/4, 속도 10배+
💬 댓글