
지난 글에서는 문자열을 글자가 순서대로 이어진 값으로 보았습니다. 그런데 장바구니에 담긴 메뉴 다섯 개, 일주일의 기온, 학생들의 점수처럼 값이 여러 개라면 변수도 다섯 개씩 만들어야 할까요?
처음 리스트를 배울 때는 대괄호만 보면 쉬워 보였습니다. 하지만 a = b로 복사했다고 생각한 뒤 한쪽 값을 바꾸자 다른 쪽까지 함께 바뀌는 순간부터 머리가 복잡해졌습니다. 이 현상은 파이썬이 이상해서가 아니라 두 이름이 같은 리스트를 보고 있기 때문입니다.
이번 글에서는 파이썬 리스트의 생성·추가·삭제·인덱싱부터 리스트와 튜플의 차이, 그리고 검색하다가 가장 자주 마주치는 리스트 복사 함정까지 한 번에 정리합니다. 지난 글의 문자열 인덱싱과 슬라이싱을 기억하면 훨씬 쉽게 이어갈 수 있습니다.
여러 값을 하나의 이름으로 묶는 이유
세 명의 점수를 각각 저장하면 다음과 같습니다.
score1 = 80
score2 = 95
score3 = 70
학생이 서른 명으로 늘어나면 변수 이름도 서른 개가 필요합니다. 평균을 구하거나 모든 점수를 출력할 때도 같은 코드를 반복해야 합니다.
리스트를 사용하면 관련 있는 값을 하나의 순서로 묶을 수 있습니다.
scores = [80, 95, 70]
print(scores)
print(len(scores))
[80, 95, 70]
3
리스트는 여러 값을 담는 상자라기보다 번호가 붙은 수납장에 가깝습니다. 값마다 순서가 있으므로 원하는 위치를 찾을 수 있고, 반복문으로 처음부터 끝까지 꺼낼 수도 있습니다.
파이썬 리스트 만들기
리스트는 대괄호 [] 안에 값을 쉼표로 구분해 만듭니다.
menus = ["김밥", "라면", "떡볶이"]
prices = [3500, 4500, 5000]
empty_list = []
menus는 문자열 세 개, prices는 정수 세 개를 담고 있습니다. empty_list는 값이 하나도 없는 빈 리스트입니다.
파이썬 리스트에는 서로 다른 자료형도 함께 넣을 수 있습니다.
mixed = ["파이썬", 10, True]
문법상 가능하지만, 한 리스트에는 같은 역할의 값을 모아 두는 편이 이해하기 쉽습니다. 메뉴 목록에는 메뉴를, 점수 목록에는 점수를 담으면 변수 이름만 보고도 의미를 짐작할 수 있습니다.
리스트 인덱싱과 슬라이싱
리스트도 문자열처럼 첫 위치를 0부터 셉니다.
fruits = ["사과", "바나나", "포도", "딸기"]
print(fruits[0])
print(fruits[-1])
print(fruits[1:3])
사과
딸기
['바나나', '포도']
fruits[0]은 첫 값이고 fruits[-1]은 마지막 값입니다. fruits[1:3]은 1번부터 3번 직전까지 가져오므로 바나나와 포도가 담긴 새 리스트가 됩니다.
문자열에서 배운 규칙과 같습니다.
- 인덱싱은 위치 하나의 값을 가져옵니다.
- 슬라이싱은 시작부터 끝 직전까지의 구간을 가져옵니다.
- 없는 위치를 인덱싱하면
IndexError가 발생합니다.
리스트는 만든 뒤에도 바꿀 수 있다
지난 글의 문자열은 특정 글자 하나를 직접 바꿀 수 없는 불변 자료형이었습니다. 리스트는 반대로 내용을 바꿀 수 있는 가변 자료형입니다.
fruits = ["사과", "바나나", "포도"]
fruits[1] = "복숭아"
print(fruits)
['사과', '복숭아', '포도']
변수 fruits에 새 리스트를 다시 넣은 것이 아니라, 이미 존재하는 리스트의 1번 자리를 바꿨습니다. 이 변경 가능성이 리스트와 튜플을 나누는 가장 중요한 기준이며, 뒤에서 살펴볼 복사 문제와도 연결됩니다.
append()로 값 하나 추가하기
리스트 끝에 값 하나를 추가할 때는 append()를 사용합니다.
shopping = ["우유", "달걀"]
shopping.append("식빵")
print(shopping)
['우유', '달걀', '식빵']
append()는 기존 리스트를 직접 변경합니다. 문자열의 strip()이 다듬어진 새 문자열을 반환했던 것과 다른 점입니다.
append()의 결과를 다시 대입하면 안 된다
다음 코드는 자주 하는 실수입니다.
shopping = ["우유", "달걀"]
shopping = shopping.append("식빵")
print(shopping)
None
append()는 리스트를 제자리에서 바꾸고 반환값으로 None을 돌려줍니다. 그래서 shopping = shopping.append(...)라고 쓰면 변경된 리스트가 아니라 None을 변수에 저장하게 됩니다.
값을 추가할 때는 다음 한 줄이면 충분합니다.
shopping.append("식빵")
insert()와 extend()는 무엇이 다를까?
원하는 위치에 값 하나를 넣을 때는 insert()를 사용합니다.
numbers = [10, 30]
numbers.insert(1, 20)
print(numbers)
[10, 20, 30]
여러 값을 리스트 끝에 이어 붙일 때는 extend()가 알맞습니다.
numbers = [10, 20]
numbers.extend([30, 40])
print(numbers)
[10, 20, 30, 40]
append([30, 40])을 사용하면 리스트 자체가 값 하나로 들어갑니다.
numbers = [10, 20]
numbers.append([30, 40])
print(numbers)
[10, 20, [30, 40]]
값 하나를 통째로 넣고 싶으면 append(), 여러 값을 펼쳐서 이어 붙이고 싶으면 extend()라고 구분하면 됩니다.
리스트 값 삭제하기
삭제 방법은 무엇을 알고 있는지에 따라 고르면 쉽습니다.
- 위치를 알면
pop(인덱스)를 사용합니다. - 값을 알면
remove(값)를 사용합니다. - 모든 값을 비우려면
clear()를 사용합니다.
tasks = ["메일", "회의", "운동"]
done = tasks.pop(0)
tasks.remove("운동")
print(done)
print(tasks)
메일
['회의']
pop()은 삭제한 값을 반환하므로 다른 변수에 저장할 수 있습니다. remove()는 같은 값이 여러 개면 첫 번째 값만 삭제하며, 찾는 값이 없으면 ValueError가 발생합니다.
리스트를 반복문과 함께 사용하기
리스트는 여러 값을 순서대로 처리할 때 특히 유용합니다.
prices = [3500, 4500, 5000]
total = 0
for price in prices:
total += price
print(f"합계: {total:,}원")
합계: 13,000원
for문이 반복될 때마다 리스트에서 값을 하나씩 꺼내 price에 연결합니다. 인덱스가 꼭 필요한 상황이 아니라면 range(len(prices))보다 값을 직접 꺼내는 방식이 간단하고 읽기 좋습니다.
값과 위치가 모두 필요할 때는 enumerate()를 사용할 수 있습니다.
menus = ["김밥", "라면", "떡볶이"]
for index, menu in enumerate(menus, start=1):
print(f"{index}. {menu}")
1. 김밥
2. 라면
3. 떡볶이
튜플은 바뀌지 않는 순서 묶음이다
튜플은 보통 소괄호 () 안에 값을 쉼표로 구분해 만듭니다.
screen_size = (1920, 1080)
rgb_red = (255, 0, 0)
print(screen_size[0])
print(rgb_red[-1])
튜플도 순서가 있으므로 인덱싱, 슬라이싱, len(), for문을 사용할 수 있습니다. 그러나 만든 뒤 특정 위치의 값을 직접 바꿀 수 없습니다.
screen_size = (1920, 1080)
screen_size[0] = 2560
이 코드는 TypeError를 발생시킵니다. 화면 크기나 좌표처럼 하나의 묶음으로 다루면서 실행 중에 함부로 바뀌면 안 되는 값에 튜플이 잘 어울립니다.
값이 하나인 튜플에는 쉼표가 필요하다
괄호만으로는 값 하나인 튜플이 되지 않습니다.
not_tuple = (10)
one_item_tuple = (10,)
print(type(not_tuple))
print(type(one_item_tuple))
<class 'int'>
<class 'tuple'>
튜플을 만드는 핵심은 괄호보다 쉼표입니다. 값이 하나일 때는 (10,)처럼 쉼표를 꼭 붙입니다.
리스트와 튜플 차이 한눈에 고르기
리스트와 튜플은 모두 순서가 있고 중복된 값을 담을 수 있습니다. 선택 기준은 어렵지 않습니다.
- 내용이 늘거나 줄고, 중간 값도 바뀐다면 리스트
[]를 사용합니다. - 한 번 정한 묶음을 그대로 유지하려면 튜플
()을 사용합니다. - 장바구니, 할 일, 점수 목록처럼 변하는 데이터는 리스트가 자연스럽습니다.
- 좌표, RGB 색상, 고정된 설정처럼 의미가 정해진 묶음은 튜플이 의도를 잘 보여 줍니다.
튜플이 언제나 리스트보다 좋거나 빠르다고 외울 필요는 없습니다. 먼저 “이 값들이 실행 중에 바뀌어야 하는가?”를 물으면 대부분의 선택이 해결됩니다.
리스트를 복사했는데 왜 같이 바뀔까?
이번 글에서 가장 중요한 함정입니다.
original = ["김밥", "라면"]
copied = original
copied.append("떡볶이")
print(original)
print(copied)
['김밥', '라면', '떡볶이']
['김밥', '라면', '떡볶이']
copied = original은 리스트를 하나 더 만든다는 뜻이 아닙니다. 같은 리스트에 original과 copied라는 이름표를 두 개 붙입니다.
original ─┐
├─> ['김밥', '라면']
copied ───┘
그래서 어느 이름으로 append()를 호출하든 둘이 함께 바라보는 하나의 리스트가 바뀝니다. 변수는 상자 자체라기보다 객체를 가리키는 이름이라는 파이썬 변수 편의 설명이 여기서 다시 중요해집니다.
서로 독립된 리스트로 복사하는 방법
바깥 리스트를 새로 만들려면 copy() 또는 전체 슬라이싱 [:]을 사용할 수 있습니다.
original = ["김밥", "라면"]
copied = original.copy()
copied.append("떡볶이")
print(original)
print(copied)
['김밥', '라면']
['김밥', '라면', '떡볶이']
다음 두 방법은 단순한 1차원 리스트에서 같은 목적에 사용할 수 있습니다.
copied1 = original.copy()
copied2 = original[:]
copied3 = list(original)
복사했는지 확인할 때는 ==와 is의 차이도 알아 두면 좋습니다.
original = [10, 20]
copied = original.copy()
print(original == copied)
print(original is copied)
True
False
==는 두 리스트의 내용이 같은지 확인하고, is는 완전히 같은 객체인지 확인합니다. 내용은 같지만 서로 독립된 리스트이므로 결과가 다릅니다.
중첩 리스트의 얕은 복사 주의하기
리스트 안에 또 다른 리스트가 있다면 copy()만으로 모든 단계가 새로 만들어지지는 않습니다.
original = [["민수", 80], ["지수", 95]]
copied = original.copy()
copied[0][1] = 100
print(original)
print(copied)
[['민수', 100], ['지수', 95]]
[['민수', 100], ['지수', 95]]
copy()는 가장 바깥 리스트만 새로 만드는 얕은 복사입니다. 안쪽 리스트는 여전히 공유하므로 내부 값을 바꾸면 양쪽에서 보입니다.
안쪽 객체까지 모두 독립적으로 복사해야 할 때는 표준 라이브러리의 copy.deepcopy()를 사용할 수 있습니다.
import copy
original = [["민수", 80], ["지수", 95]]
copied = copy.deepcopy(original)
copied[0][1] = 100
print(original)
print(copied)
모든 리스트에 무조건 깊은 복사가 필요한 것은 아닙니다. 숫자나 문자열만 담긴 단순한 리스트는 copy()로 충분합니다. 중첩된 변경 가능 객체가 있고, 그 내부까지 따로 바꿔야 할 때 deepcopy()를 선택하면 됩니다.
자주 하는 실수
append()와 extend()를 혼동하는 경우
append([3, 4])는 리스트를 값 하나로 넣고, extend([3, 4])는 3과 4를 각각 이어 붙입니다. 원하는 결과의 대괄호 모양을 먼저 떠올려 보세요.
remove()에 위치 번호를 넣는 경우
remove(1)은 1번 위치가 아니라 값 1을 찾습니다. 위치로 삭제하려면 pop(1)을 사용합니다.
대입을 복사라고 생각하는 경우
copied = original은 같은 리스트에 이름을 하나 더 붙입니다. 독립된 바깥 리스트가 필요하면 original.copy()를 사용합니다.
튜플 값 하나에서 쉼표를 빼는 경우
(10)은 정수이고 (10,)이 값 하나인 튜플입니다.
직접 바꿔 보며 확인하기
다음 문제를 직접 작성해 보세요.
- 좋아하는 메뉴 세 개를 리스트로 만들고 첫 메뉴와 마지막 메뉴를 출력합니다.
append(),insert(),remove()를 차례로 사용해 장보기 목록을 수정합니다.- 가격 리스트를
for문으로 반복해 합계를 구합니다. - 화면 해상도
(1920, 1080)을 튜플로 만들고 각 값을 출력합니다. copied = original과copied = original.copy()의 결과를 비교합니다.- 중첩 리스트를 얕게 복사한 뒤 안쪽 값을 바꾸고 원본도 변하는지 확인합니다.
코드를 실행하기 전에 결과를 먼저 예상해 보세요. 특히 두 변수의 화살표가 같은 리스트를 가리키는지, 서로 다른 리스트를 가리키는지 종이에 그려 보면 복사 문제가 훨씬 선명해집니다.
정리
파이썬 리스트는 여러 값을 순서대로 담고 실행 중에 추가·삭제·수정할 수 있는 가변 자료형입니다. 인덱싱과 슬라이싱은 문자열에서 배운 규칙을 그대로 사용하며, append(), insert(), extend(), pop(), remove()로 내용을 바꿀 수 있습니다.
튜플도 순서가 있지만 만든 뒤 항목을 바꿀 수 없습니다. 변하는 목록에는 리스트를, 하나의 고정된 묶음에는 튜플을 사용하면 의도가 분명해집니다.
마지막으로 copied = original은 복사가 아니라 같은 객체를 함께 가리키는 대입입니다. 독립된 바깥 리스트는 copy()로 만들고, 중첩된 객체 내부까지 분리해야 할 때만 copy.deepcopy()를 사용합니다.
다음 글에서는 이름과 값의 관계를 저장하는 딕셔너리와 중복을 제거하고 집합 연산을 할 수 있는 집합을 알아보겠습니다.
참고 자료
'파이썬(Python)' 카테고리의 다른 글
| [Python(파이썬)] 함수 만들기: def·매개변수·return 차이 쉽게 이해하기 (0) | 2026.08.14 |
|---|---|
| [Python(파이썬)] 딕셔너리 사용법과 set 중복 제거, 한 번에 이해하기 (0) | 2026.08.14 |
| [Python(파이썬)] 문자열이란? 인덱싱·슬라이싱·메서드와 f-string 쉽게 이해하기 (0) | 2026.08.12 |
| [Python(파이썬)] while문이란? 종료 조건과 break·continue 쉽게 이해하기 (0) | 2026.08.11 |
| [Python(파이썬)] for문이란? range()와 누적 계산 쉽게 이해하기 (0) | 2026.08.10 |