파이썬 프로그래밍에서 데이터를 효율적으로 다루는 것은 매우 중요합니다. 리스트는 여러 데이터를 순서대로 담아 관리할 수 있는 가장 기본적인 형태의 데이터 묶음입니다. 이 챕터에서는 리스트를 생성하고 다양한 방법으로 조작하는 방법을 실습하며 파이썬 데이터 활용의 첫걸음을 떼보겠습니다.
리스트(List)란 무엇인가요?
파이썬 리스트는 순서가 있는 변경 가능한(mutable) 데이터들의 집합입니다. 제가 데이터 분석 업무를 할 때 가장 자주 사용하는 데이터 구조 중 하나가 바로 이 리스트입니다. 마치 물건을 담는 장바구니와 같아서, 원하는 순서대로 아이템을 넣고 빼거나, 나중에 순서를 바꾸는 등 자유롭게 관리할 수 있습니다. 문자열, 숫자뿐만 아니라 다른 리스트까지도 담을 수 있어 활용도가 매우 높습니다.
예를 들어, 쇼핑 목록이나 학생들의 점수 목록 등 순서가 중요하고 내용이 바뀔 수 있는 데이터를 다룰 때 리스트가 제격입니다. 변수에 데이터를 저장하는 기본을 익히셨다면, 이제 이 변수들을 한데 묶어 관리하는 방법을 배울 차례입니다.
리스트 생성하기: 다양한 방법
리스트는 대괄호([])를 사용하여 만들며, 쉼표(,)로 각 요소를 구분합니다. 빈 리스트를 만들 수도 있고, 처음부터 다양한 자료형을 가진 요소들을 포함할 수도 있습니다. 제 경험상, 처음에는 빈 리스트로 시작해서 필요에 따라 요소를 추가하는 경우가 많습니다.
빈 리스트 생성
가장 기본적으로 빈 리스트를 만드는 방법입니다. 데이터를 나중에 채워 넣을 때 유용합니다.
# 빈 리스트 생성하기
my_list = []
print(my_list) # 결과: []
print(type(my_list)) # 결과: <class 'list'>
이 코드가 하는 일: ‘my_list’라는 이름의 빈 리스트를 생성하고 그 내용을 출력합니다.
초기 요소를 포함한 리스트 생성
여러 종류의 데이터를 한 리스트에 함께 담을 수 있습니다. 파이썬 리스트는 정수, 실수, 문자열 등 숫자형 데이터나 문자열 같은 여러 데이터 타입을 한 리스트에 섞어 넣을 수 있다는 장점이 있습니다. 이는 다른 언어와 비교했을 때 파이썬이 가지는 유연함 중 하나입니다.
# 여러 종류의 데이터를 담은 리스트
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed_list = ["Python", 3, True, 3.14]
print(fruits) # 결과: ['apple', 'banana', 'cherry']
print(numbers) # 결과: [1, 2, 3, 4, 5]
print(mixed_list) # 결과: ['Python', 3, True, 3.14]
이 코드가 하는 일: 문자열, 숫자, 혼합된 데이터 타입으로 세 가지 리스트를 생성하고 각각 출력합니다.
리스트 요소 접근하기: 인덱싱
리스트의 특정 요소에 접근하려면 인덱스(index)를 사용합니다. 인덱스는 0부터 시작하는 순서를 나타내는 숫자입니다. 제 경우, 처음 파이썬을 배울 때 0부터 시작하는 인덱싱 때문에 가끔 실수를 했지만, 금방 익숙해집니다.
양수 인덱싱
리스트의 첫 번째 요소는 인덱스 0, 두 번째는 1과 같이 양수 인덱스를 사용하여 접근합니다.
my_list = ["a", "b", "c", "d", "e"]
print(my_list[0]) # 결과: a (첫 번째 요소)
print(my_list[2]) # 결과: c (세 번째 요소)
이 코드가 하는 일: ‘my_list’의 첫 번째와 세 번째 요소에 인덱스를 사용하여 접근하고 출력합니다.
음수 인덱싱
리스트의 마지막 요소부터 역순으로 접근할 때는 음수 인덱스를 사용합니다. -1은 마지막 요소, -2는 마지막에서 두 번째 요소를 의미합니다. 이 기능은 특히 리스트의 끝에 있는 요소에 빠르게 접근할 때 매우 유용합니다.
my_list = ["a", "b", "c", "d", "e"]
print(my_list[-1]) # 결과: e (마지막 요소)
print(my_list[-3]) # 결과: c (마지막에서 세 번째 요소)
이 코드가 하는 일: ‘my_list’의 마지막 요소와 마지막에서 세 번째 요소에 음수 인덱스를 사용하여 접근하고 출력합니다.
인덱스 에러 (IndexError)
존재하지 않는 인덱스에 접근하려 하면 IndexError가 발생합니다. 이는 제가 초보 시절에 정말 자주 만났던 에러 중 하나입니다. 예를 들어, 길이가 5인 리스트에서 인덱스 5에 접근하려고 하면 에러가 납니다.
my_list = [10, 20, 30]
# print(my_list[3]) # IndexError: list index out of range (주석 처리)
이 코드가 하는 일: 주석 처리된 라인을 실행하면, 리스트 길이를 넘어서는 인덱스에 접근하여 오류가 발생함을 보여줍니다.
리스트 수정하기: 요소 변경, 추가, 삭제
리스트는 생성 후에도 자유롭게 내용을 변경할 수 있는 ‘변경 가능한(mutable)’ 데이터 구조입니다. 이는 튜플처럼 변경 불가능한(immutable) 데이터 묶음과 가장 큰 차이점입니다.
요소 값 변경
특정 인덱스의 값을 새로운 값으로 대체하여 리스트 요소를 변경할 수 있습니다.
fruits = ["apple", "banana", "cherry"]
fruits[1] = "orange" # banana를 orange로 변경
print(fruits) # 결과: ['apple', 'orange', 'cherry']
이 코드가 하는 일: ‘fruits’ 리스트의 두 번째 요소를 ‘orange’로 변경하고 출력합니다.
요소 추가: append(), insert()
리스트에 새로운 요소를 추가하는 방법은 append()와 insert()가 있습니다. append()는 리스트의 맨 끝에 요소를 추가하고, insert(index, item)는 특정 인덱스 위치에 요소를 삽입합니다.
my_list = [1, 2, 3]
my_list.append(4) # 맨 끝에 4 추가
print(my_list) # 결과: [1, 2, 3, 4]
my_list.insert(1, 0) # 인덱스 1 위치에 0 삽입
print(my_list) # 결과: [1, 0, 2, 3, 4]
이 코드가 하는 일: append()와 insert() 메서드를 사용하여 리스트에 요소를 추가하고 변화를 출력합니다.
요소 삭제: remove(), pop(), del
리스트에서 요소를 삭제하는 방법은 크게 세 가지가 있습니다: remove(), pop(), del 키워드. 각각의 쓰임새가 다르므로 상황에 맞춰 선택하는 것이 중요합니다. 현업에서는 주로 pop()이나 del을 사용하여 인덱스로 삭제하는 경우가 많습니다.
remove(value): 값으로 삭제
remove() 메서드는 리스트에서 특정 ‘값’을 찾아 첫 번째로 일치하는 요소를 삭제합니다. 만약 리스트에 없는 값을 삭제하려 하면 ValueError가 발생합니다.
my_list = ["apple", "banana", "cherry", "banana"]
my_list.remove("banana")
print(my_list) # 결과: ['apple', 'cherry', 'banana']
# my_list.remove("grape") # ValueError: list.remove(x): x not in list (주석 처리)
이 코드가 하는 일: ‘my_list’에서 ‘banana’라는 값을 가진 첫 번째 요소를 삭제합니다.
pop(index): 인덱스로 삭제 및 반환
pop() 메서드는 특정 인덱스의 요소를 삭제하고, 그 삭제된 요소를 반환합니다. 인덱스를 지정하지 않으면 기본적으로 맨 마지막 요소를 삭제하고 반환합니다.
my_list = ["a", "b", "c", "d"]
removed_item = my_list.pop(1) # 인덱스 1의 요소 'b' 삭제 및 반환
print(removed_item) # 결과: b
print(my_list) # 결과: ['a', 'c', 'd']
last_item = my_list.pop() # 맨 마지막 요소 'd' 삭제 및 반환
print(last_item) # 결과: d
print(my_list) # 결과: ['a', 'c']
이 코드가 하는 일: pop() 메서드를 사용하여 특정 인덱스의 요소와 마지막 요소를 삭제하고 그 반환값을 출력합니다.
del 키워드: 인덱스 또는 슬라이스로 삭제
del 키워드는 특정 인덱스의 요소를 삭제하거나, 슬라이싱을 이용하여 여러 요소를 한 번에 삭제할 수 있습니다. pop()과 달리 삭제된 값을 반환하지는 않습니다.
my_list = [10, 20, 30, 40, 50]
del my_list[2] # 인덱스 2의 30 삭제
print(my_list) # 결과: [10, 20, 40, 50]
del my_list[1:3] # 인덱스 1부터 2까지 (20, 40) 삭제
print(my_list) # 결과: [10, 50]
이 코드가 하는 일: del 키워드를 사용하여 특정 인덱스와 슬라이싱 범위의 요소를 삭제하고 리스트 변화를 출력합니다.
리스트 슬라이싱: 부분 리스트 추출
슬라이싱은 리스트의 특정 범위를 잘라내어 새로운 리스트를 만드는 강력한 기능입니다. 문자열 슬라이싱과 거의 동일하게 동작하며, 데이터 분석 시 필요한 부분만 빠르게 추출할 때 특히 유용합니다.
list[start:end:step] 형태로 사용하며, start는 포함하고 end는 포함하지 않습니다. step은 건너뛰는 간격을 의미하며 생략 가능합니다.
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5]) # 결과: [2, 3, 4] (인덱스 2부터 5 전까지)
print(numbers[:3]) # 결과: [0, 1, 2] (처음부터 인덱스 3 전까지)
print(numbers[7:]) # 결과: [7, 8, 9] (인덱스 7부터 끝까지)
print(numbers[::2]) # 결과: [0, 2, 4, 6, 8] (처음부터 끝까지 2칸씩 건너뛰며)
print(numbers[::-1]) # 결과: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (리스트 뒤집기)
이 코드가 하는 일: 다양한 슬라이싱 방법을 사용하여 ‘numbers’ 리스트의 부분 리스트를 추출하고 출력합니다.
리스트의 유용한 메서드들
파이썬 리스트는 데이터를 효과적으로 관리하기 위한 다양한 내장 메서드를 제공합니다. 이 메서드들을 잘 활용하면 코드를 간결하고 효율적으로 작성할 수 있습니다. 2024년 기준, 제가 현업에서 가장 자주 사용하는 메서드는 len(), sort(), count() 정도입니다.
len(): 리스트 길이 확인
len() 함수는 리스트에 몇 개의 요소가 있는지, 즉 리스트의 길이를 반환합니다.
my_list = ["a", "b", "c", "d"]
print(len(my_list)) # 결과: 4
이 코드가 하는 일: ‘my_list’의 길이를 측정하여 출력합니다.
sort(), sorted(): 리스트 정렬
sort() 메서드는 리스트 자체를 정렬하고, sorted() 함수는 정렬된 새로운 리스트를 반환합니다. sort()는 원본 리스트를 변경한다는 점을 기억해야 합니다. 만약 원본을 유지하고 싶다면 sorted()를 사용해야 합니다.
numbers = [3, 1, 4, 1, 5, 9, 2]
numbers.sort() # 원본 리스트 정렬
print(numbers) # 결과: [1, 1, 2, 3, 4, 5, 9]
fruits = ["banana", "apple", "cherry"]
sorted_fruits = sorted(fruits) # 정렬된 새 리스트 반환
print(sorted_fruits) # 결과: ['apple', 'banana', 'cherry']
print(fruits) # 결과: ['banana', 'apple', 'cherry'] (원본은 그대로)
이 코드가 하는 일: sort() 메서드와 sorted() 함수를 사용하여 리스트를 정렬하고 결과를 출력합니다.
count(), index(): 요소 개수 세기 및 위치 찾기
count()는 특정 요소가 리스트에 몇 번 나타나는지 세어주고, index()는 특정 요소의 첫 번째 위치(인덱스)를 찾아줍니다.
my_list = [1, 2, 2, 3, 2, 4]
print(my_list.count(2)) # 결과: 3 (2가 3번 나타남)
print(my_list.index(3)) # 결과: 3 (3은 인덱스 3에 위치)
이 코드가 하는 일: count()로 특정 값의 출현 횟수를, index()로 특정 값의 첫 인덱스를 찾아 출력합니다.
리스트 복사: 얕은 복사와 깊은 복사
리스트를 다룰 때 가장 흔히 실수하는 부분 중 하나가 ‘복사’입니다. 단순히 new_list = old_list처럼 대입하면 두 변수가 같은 리스트를 가리키게 됩니다. 이를 ‘얕은 복사’라고 합니다. 한쪽을 수정하면 다른 쪽도 함께 변하는 문제가 발생합니다. 제가 취미로 개발하던 시절, 이 때문에 몇 번이나 예상치 못한 버그를 경험했습니다.
얕은 복사 (Shallow Copy)
대입 연산자(=)를 사용하면 리스트의 참조만 복사되어 두 변수가 같은 리스트를 공유합니다.
original = [1, 2, 3]
shallow_copy = original # 얕은 복사
shallow_copy[0] = 99
print(original) # 결과: [99, 2, 3] (원본도 변경됨!)
이 코드가 하는 일: 얕은 복사의 문제점을 보여주기 위해 원본 리스트가 복사본 수정을 통해 함께 변경되는 과정을 출력합니다.
깊은 복사 (Deep Copy)
리스트의 내용을 독립적으로 복사하려면 슬라이싱([:])이나 copy() 메서드를 사용해야 합니다.
original = [1, 2, 3]
deep_copy_slice = original[:] # 슬라이싱을 이용한 깊은 복사
deep_copy_method = original.copy() # copy() 메서드를 이용한 깊은 복사
deep_copy_slice[0] = 100
deep_copy_method[1] = 200
print(original) # 결과: [1, 2, 3] (원본은 변경되지 않음)
print(deep_copy_slice) # 결과: [100, 2, 3]
print(deep_copy_method) # 결과: [1, 200, 3]
이 코드가 하는 일: 슬라이싱과 copy() 메서드를 사용한 깊은 복사를 통해 원본 리스트가 보호됨을 보여줍니다.
만약 리스트 안에 또 다른 리스트(중첩 리스트)가 있다면, copy()나 슬라이싱은 여전히 ‘얕은 복사’로 작동합니다. 이때는 copy 모듈의 deepcopy() 함수를 사용해야 하지만, 이는 초급 과정의 범위를 넘어섭니다. 지금은 기본적인 깊은 복사 방법을 이해하는 것만으로도 충분합니다.
리스트와 다른 데이터 구조와의 관계
리스트는 다른 데이터 구조와 상호 변환하며 다양한 상황에 활용될 수 있습니다. 예를 들어, 튜플이나 세트, 딕셔너리 등 파이썬의 다른 데이터 구조 간 변환을 통해 리스트의 유연성을 더욱 높일 수 있습니다.
특히, 리스트는 반복문과 함께 사용될 때 강력한 시너지를 발휘합니다. for 반복문을 사용하여 시퀀스 데이터를 순회하는 방법을 배우고 나면, 리스트 안의 모든 요소를 하나씩 처리하는 코드를 쉽게 작성할 수 있게 됩니다. 실제 업무에서는 이 패턴을 굉장히 자주 사용합니다.
마무리하며
이번 챕터에서는 파이썬 리스트의 기본적인 생성부터 시작하여 요소 접근, 수정, 삭제, 그리고 슬라이싱과 다양한 유용한 메서드들까지 폭넓게 다루어 보았습니다. 리스트는 파이썬에서 가장 중요한 데이터 구조 중 하나이며, 데이터를 다루는 프로그래밍의 핵심적인 도구입니다.
제가 처음 파이썬을 배울 때, 리스트의 유연함과 강력함에 매료되었던 기억이 납니다. 직접 코드를 작성하고 실행해보면서 각 기능이 어떻게 동작하는지 손으로 익히는 것이 중요합니다. 다양한 예제를 통해 오늘 배운 리스트 조작법을 숙달해 보시기 바랍니다.
참고 자료
- [1] Python 공식 튜토리얼 — 자료 구조 — https://docs.python.org/ko/3/tutorial/datastructures.html
- [2] Python 표준 라이브러리 — 내장 자료형 — https://docs.python.org/ko/3/library/stdtypes.html
