본문 바로가기
AI/Coding

파이썬 입문 4편 — 리스트(list) 완전 정복: 추가·삭제·정렬부터 복사 함정까지

by 거대웅 TitanBear 2026. 6. 11.

📚 「파이썬 완전 정복」 시리즈 4편

3편까지 우리는 숫자와 문자열, 그러니까 "값 하나"를 다루는 법을 익혔습니다. 그런데 실제 프로그램은 값 하나로 굴러가지 않죠. 장바구니에 담긴 물건 열 개, 반 학생 서른 명, 한 달 가계부 내역 수백 줄… 이번 편의 주인공은 이렇게 여러 값을 한 바구니에 담는 리스트(list)입니다.

솔직히 말하면, 파이썬에서 가장 많이 쓰는 자료형을 딱 하나만 꼽으라면 저는 망설임 없이 리스트를 고릅니다. 그만큼 자주 쓰고, 그만큼 초보자가 자주 헷갈리는 녀석이기도 합니다. 오늘 한 편으로 리스트를 만들고, 넣고, 빼고, 정렬하고, 마지막으로 초보자 열에 아홉이 당하는 "복사 함정"까지 깔끔하게 끝내 봅시다.

이 편에서 배우는 것

① 리스트를 만들고 꺼내 쓰기   ② 값 추가 — append · insert · extend   ③ 값 삭제 — remove · pop · del   ④ 찾기·세기·정렬 — index · count · sort · sorted   ⑤ 반복문과 리스트 컴프리헨션   ⑥ 가장 많이 당하는 복사·참조 함정

1. 변수 서른 개를 만들 건가요?

잠깐 상상해 봅시다. 반 학생 서른 명의 점수를 저장해야 합니다. 변수로 한다면 score1, score2, … score30? 생각만 해도 아찔합니다. 평균이라도 내려면 서른 개를 일일이 더해야 하고요. 이럴 때 값들을 대괄호 [ ] 하나에 쉼표로 묶어 담는 것이 리스트입니다.

cart = ["우유", "달걀", "식빵"]
print(cart)          # ['우유', '달걀', '식빵']
print(len(cart))     # 3  (담긴 항목 개수)
print(cart[0])       # 우유   (0번부터 시작)
print(cart[-1])      # 식빵   (음수는 뒤에서부터)

# 리스트 안에는 자료형을 섞어 담을 수도 있습니다
mixed = [1, "둘", 3.0, True]
print(mixed)         # [1, '둘', 3.0, True]

인덱싱과 슬라이싱은 3편에서 문자열로 연습한 그 문법 그대로입니다. 문자열이 "글자들의 줄"이었다면, 리스트는 "값들의 줄"일 뿐이에요. 그래서 슬라이싱도 똑같이 통합니다.

nums = [10, 20, 30, 40, 50]
print(nums[1:4])   # [20, 30, 40]
print(nums[:3])    # [10, 20, 30]
print(nums[::-1])  # [50, 40, 30, 20, 10]  (뒤집기)

2. 값 넣기 — append, insert, extend의 미묘한 차이

리스트에 값을 넣는 방법은 크게 세 가지인데, 이 셋을 헷갈리면 엉뚱한 결과가 나옵니다. 하나씩 봅시다.

todo = ["청소"]

todo.append("빨래")        # 맨 뒤에 하나 추가
print(todo)                # ['청소', '빨래']

todo.insert(0, "설거지")    # 0번 자리에 끼워 넣기
print(todo)                # ['설거지', '청소', '빨래']

todo.extend(["장보기", "운동"])  # 다른 리스트를 통째로 이어 붙이기
print(todo)                # ['설거지', '청소', '빨래', '장보기', '운동']

여기서 초보자가 가장 많이 하는 실수 하나. append에 리스트를 넣으면 어떻게 될까요? ["장보기", "운동"]append하면 리스트 안에 리스트가 통째로 한 칸으로 들어갑니다. "이어 붙이기"를 원했다면 extend가 정답이에요. 저도 입문 때 이걸로 한참 헤맸습니다.

3. 값 빼기 — remove, pop, del

넣었으면 뺄 줄도 알아야죠. 삭제도 상황에 따라 도구가 다릅니다. 값으로 지울지, 위치로 지울지를 먼저 생각하면 헷갈리지 않습니다.

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

nums.remove(20)    # "값" 20을 지움 (처음 만난 하나만!)
print(nums)        # [10, 30, 20, 40]

last = nums.pop()  # 맨 뒤를 꺼내면서 그 값을 돌려줌
print(last, nums)  # 40 [10, 30, 20]

second = nums.pop(1)  # 1번 위치를 꺼내 돌려줌
print(second, nums)   # 30 [10, 20]

del nums[0]        # 0번 위치를 그냥 삭제
print(nums)        # [20]

한 가지 짚고 갈 점. remove는 중복된 값이 여러 개여도 가장 앞에 있는 것 하나만 지웁니다. 위 예제에서 20이 두 개였지만 하나만 사라졌죠. 전부 지우고 싶다면 반복문이나 컴프리헨션을 써야 하는데, 그건 잠시 뒤에 나옵니다.

4. 찾고, 세고, 정렬하기

실무에서 리스트를 쓰는 이유의 절반은 "정렬"과 "검색" 때문입니다. 시험 점수를 예로 들어 보죠.

scores = [88, 92, 75, 92, 60]

print(scores.index(92))  # 1     (92가 처음 등장하는 위치)
print(scores.count(92))  # 2     (92가 몇 번 나오는지)
print(92 in scores)      # True  (있는지 없는지)
print(max(scores), min(scores), sum(scores))  # 92 60 407

scores.sort()            # 원본을 오름차순으로 정렬
print(scores)            # [60, 75, 88, 92, 92]

scores.sort(reverse=True)  # 내림차순
print(scores)            # [92, 92, 88, 75, 60]

여기서 꼭 구분해야 할 짝이 있습니다. sort()원본 리스트 자체를 바꿔 버리고, sorted()원본은 그대로 두고 정렬된 새 리스트를 돌려줍니다. 원본을 지켜야 한다면 후자를 쓰세요.

words = ["banana", "apple", "cherry"]
print(sorted(words))   # ['apple', 'banana', 'cherry']
print(words)           # ['banana', 'apple', 'cherry']  (원본 그대로!)
하고 싶은 것 메서드 원본이 바뀌나?
맨 뒤에 추가 append(x)
중간에 끼우기 insert(i, x)
값으로 삭제 remove(x)
위치로 꺼내기 pop(i) 예 (값 반환)
원본째 정렬 sort()
새 리스트로 정렬 sorted(li) 아니오

5. 리스트와 반복문, 그리고 컴프리헨션

리스트는 for 반복문과 짝꿍입니다. 항목을 하나씩 꺼내 처리할 때 enumerate를 곁들이면 번호까지 자동으로 매겨 줍니다.

basket = ["사과", "바나나", "포도"]
for i, fruit in enumerate(basket, 1):   # 1번부터 번호 매기기
    print(f"{i}번: {fruit}")
# 1번: 사과
# 2번: 바나나
# 3번: 포도

그리고 파이썬을 파이썬답게 만드는 문법, 리스트 컴프리헨션이 있습니다. "1부터 5까지 각각 제곱한 리스트를 만들어라" 같은 작업을 한 줄로 끝냅니다. 처음엔 낯설어도 익숙해지면 이만큼 편한 게 없어요.

squares = [n * n for n in range(1, 6)]
print(squares)   # [1, 4, 9, 16, 25]

# 뒤에 조건을 붙이면 "걸러내기"도 됩니다
evens = [n for n in range(10) if n % 2 == 0]
print(evens)     # [0, 2, 4, 6, 8]

6. 가장 많이 당하는 함정 — 복사와 참조

자, 오늘의 하이라이트입니다. 이걸 모르면 언젠가 반드시 한 번은 크게 데입니다. 다음 코드의 결과를 예상해 보세요.

a = [1, 2, 3]
b = a          # "복사"한 것 같지만…
b.append(4)
print(a)       # [1, 2, 3, 4]  (?!) a까지 바뀌었다

⚠️ 흔한 실수 — b = a는 복사가 아닙니다

b = a는 같은 리스트에 이름표를 하나 더 붙이는 것뿐입니다. 택배 상자 하나에 운송장만 두 장 붙인 셈이죠. b를 건드리면 같은 상자인 a도 당연히 바뀝니다. 진짜 복사가 필요하면 a.copy() 또는 a[:]를 쓰세요.

a = [1, 2, 3, 4]
c = a.copy()   # 내용물을 새 상자에 옮겨 담기
c.append(5)
print(a)       # [1, 2, 3, 4]      (영향 없음)
print(c)       # [1, 2, 3, 4, 5]

이 함정은 2차원 리스트(리스트의 리스트)에서 한 번 더 변형되어 나타납니다. 격자판을 곱셈으로 만들려다 모두가 한 번씩 당하는 코드예요.

grid = [[0] * 3] * 2   # 0이 셋인 줄을 2개?
grid[0][0] = 9
print(grid)            # [[9, 0, 0], [9, 0, 0]]  (둘 다 바뀐다!)

# 올바른 방법 — 컴프리헨션으로 매번 새 줄을 만든다
grid2 = [[0] * 3 for _ in range(2)]
grid2[0][0] = 9
print(grid2)           # [[9, 0, 0], [0, 0, 0]]

💡 실무 팁 — 리스트를 보기 좋게 출력할 땐 join이 최고입니다. print(", ".join(["사과","바나나","포도"]))사과, 바나나, 포도. 단, join은 문자열 리스트에만 통하니 숫자 리스트는 먼저 문자열로 바꿔야 한다는 점만 기억하세요.

리스트의 90%는 이 문장으로 요약됩니다 — 대괄호로 묶고, append로 넣고, pop으로 빼고, sort로 줄 세우고, for로 훑는다. 그리고 b = a는 복사가 아니다.

마무리 & 연습문제 (정답 포함)

눈으로만 읽으면 다음 날 절반은 날아갑니다. 꼭 직접 쳐 보세요.

# 문제 1) 점수 리스트에서 최고점과 평균 구하기
scores = [88, 92, 75, 60, 95]
print(max(scores))                    # 95
print(sum(scores) / len(scores))      # 82.0

# 문제 2) 리스트에서 짝수만 골라 새 리스트로
nums = [1, 2, 3, 4, 5, 6]
print([n for n in nums if n % 2 == 0])   # [2, 4, 6]

# 문제 3) 이름 리스트를 가나다순으로 정렬해 한 줄로 출력
names = ["지민", "가영", "다은", "나리"]
names.sort()
print(", ".join(names))               # 가영, 나리, 다은, 지민

더 공부할 거리

다음 편 예고 — 5편 · 리스트 정렬의 모든 것
이번 편에서 맛만 본 sortsorted를 제대로 파고듭니다. key 옵션으로 "길이순", "두 번째 글자순", "성적 높은 순" 같은 나만의 정렬 기준을 만드는 법까지 — 실무에서 진짜 쓰는 정렬 기술을 배웁니다.