파이썬 리스트(list) 완벽 가이드
파이썬에서 리스트(list)는 사전(dictionary)과 더불어 가장 널리 사용되는 내장 자료형입니다. 이 두 가지 자료형은 파이썬 프로그래밍에서 필수적인 요소로 사용되지 않은 프로그램을 보기 힘들 정도이죠.
이번 포스팅에서는 다양한 예제를 통해서 파이썬에서 리스트를 어떻게 사용하는지 아주 꼼꼼하게 차근차근 알아보도록 하겠습니다.
참고로 사전(dictionary)에 대해서는 별도의 포스팅에서 아주 자세히 다루고 있습니다!
리스트의 특징
리스트(list)는 여러 데이터를 순서있게 담기 위해서 파이썬에서 가장 범용적으로 사용되는 자료형인데요. 가변(mutable) 자료형이기 때문에 리스트를 생성 후에 자유롭게 새로운 데이터를 추가하거나 기존 데이터를를 변경 또는 제거할 수 있습니다.
동적 타이핑(dynamic typing) 언어인 파이썬에서는 하나의 리스트 안에 여러 타입의 데이터를 막 섞어서 저장할 수 있는데요. 자바(Java)와 같이 선언할 때 저장할 데이터의 타입을 명시하고 다른 타입의 데이터는 제한하는 정적 타이핑(static typing) 언어와 비교했을 때 대조되는 부분입니다.
같은 동적 언어 계열인 자바스크립트(JavaScript)에 익숙하시다면 자연스럽게 배열(array)을 떠올리시게 되실텐데요. 실제로 파이썬의 리스트와 자바스크립트의 배열 간에는 이러한 유연함에 있어서 상당히 유사히지만 실제 문법에는 꽤 차이가 나는 것 같습니다.
여러 데이터를 담을 수 있는 불변(immutable)한 자료형이 필요하시다면 리스트 대신에 튜플(tuple)이라는 자료형을 사용하셔야 합니다. 이 부분은 관련 포스팅를 참고 바랍니다.
리스트 생성
리스트는 여러 가지 방법으로 생성할 수 있는데요.
가장 간편한 방법은 대괄호([]
) 안에 여러 원소를 쉼표(,
)로 구분해서 나열해주는 것입니다.
다음과 같이 타입에 구애 받지 않고 다양한 데이터를 하나의 리스트에 저장할 수 있습니다.
>>> [1, 3.14, 'A', True, None, {'b': 2}, ['c', 'd'], lambda x: x * x]
[1, 3.14, 'A', True, None, {'b': 2}, ['c', 'd'], <function <lambda> at 0x1012471a0>]
사실 빈 리스트을 생성할 때도 자주 있는데요.
가변 자료형이라 나중에 언제든지 데이터를 추가할 수 있기 때문입니다.
빈 리스트을 생성할 때는 빈 대괄호를 사용하거나, list()
내장 함수를 호출하면 됩니다.
>>> []
[]
>>> list()
[]
여기까지만 보시면 list()
함수가 크게 쓸모 없다고 생각하실 수 있지만, 사실 list()
함수는 다른 자료형을 리스트로 변환할 때 매우 유용하게 쓸 수 있습니다.
다음 예제를 통해 유사한 성격을 띄고 있는 다양한 자료형을 상대로 list()
를 활용하는 다양한 방법을 볼 수 있습니다.
>>> list({'A', 'B', 'C'}) # 튜플을 리스트로 변환
['A', 'B', 'C']
>>> list(range(10)) # range 객체를 리스트로 변환
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(enumerate(['A', 'B', 'C'])) # enumerate 객체를 리스트로 변환
[(0, 'A'), (1, 'B'), (2, 'C')]
데이터 추가
리스트에 데이터를 추가하는 방법은 여러가지가 있는데요.
가장 기본이 되는 방법은 append()
메서드에 추가할 원소를 인수로 넘기는 것입니다.
>>> fruits = []
>>> fruits.append('Apple')
>>> fruits.append('Banana')
>>> fruits.append('Cherry')
>>> fruits
['Apple', 'Banana', 'Cherry']
두 번째 방법은 insert()
메서드를 이용하는 건데요.
insert()
메서드는 첫번째 인자로 인덱스(index)를 받고, 두번째 인자로 데이터를 받기 때문에 데이터를 정확히 원하는 위치에 삽입할 수 있습니다.
>>> fruits.insert(0, 'Mongo')
>>> fruits
['Mongo', 'Apple', 'Banana', 'Cherry']
>>> fruits.insert(1, 'Orange')
>>> fruits
['Mongo', 'Orange', 'Apple', 'Banana', 'Cherry']
여기서 append()
대신에 insert()
를 사용할 실 때는 꼭 염두하실 부분이 있는데요.
파이썬의 리스트는 다른 언어의 배열처럼 무작위 접근(random access)에 최적화된 자료구조이기 때문에
insert()
메서드를 사용하게 되면 기존에 배열 내에 있던 많은 원소를 뒤로 한 칸씩 밀어야 되서 성능상 불리합니다.
따라서 많은 데이터가 저장된 리스트를 다루거나 성능에 민감한 프로그램에서는 가급적 insert()
메서드의 사용을 피하는 것이 좋으며,
원소를 리스트의 앞에서 넣을 일이 많다면 deque
나 Queue
와 같은 자료형을 고려해보시는 것이 좋습니다.
파이썬에서 큐 자료구조가 필요할 때 사용하는
deque
와Queue
에 대해서는 관련 포스팅에서 자세히 다루고 있으니 참고 바랍니다.
데이터 접근
이제 리스트에 추가한 데이터를 읽어볼까요?
리스트가 담고 있는 데이터에는 순서가 있기 때문에 인덱스(index)를 통해서 접근하는데요. 대괄호에 인덱스를 넘기면 됩니다.
>>> fruits[0]
'Mongo'
>>> fruits[2]
'Apple'
다른 프로그래밍 언어와 달리 파이썬의 리스트가 재미있는 부분은 바로 음수 인덱스도 지원한다는 것입니다.
>>> fruits[-1] # fruits[len(fruits) - 1]
'Cherry'
>>> fruits[-3] # fruits[len(fruits) - 3]
'Apple'
이것을 잘 활용하면 좀 더 간편하게 리스트의 뒷 부분에 저장된 데이터에 접근할 수 있습니다.
리스트 슬라이싱
파이썬에서는 리스트 내의 특정 범위의 데이터 접근을 용이하도록 슬라이싱(slicing)이라는 매우 매력적인 문법을 제공하는데요.
대괄호 안에 :
기호를 사용해서 접근을 원하는 범위의 시작 인덱스과 종료 인덱스를 명시하면 해당 범위 내의 원소들이 리스트에 담겨서 반환이 됩니다.)
>>> fruits = ['Mongo', 'Orange', 'Apple', 'Banana', 'Cherry']
>>> fruits[1:3]
['Orange', 'Apple']
슬라이싱 문법을 사용하실 때는 시작 인덱스에 있는 원소는 포함하고, 종료 인덱스에 있는 원소는 포함하지 않는 부분에 주의 바랍니다.
시작 인덱스를 생략하면 리스트의 맨 앞부터 범위가 시작하며, 종료 인덱스를 생략하면 리스트의 맨 뒤까지 범위에 포함됩니다.
>>> fruits[:3]
['Mongo', 'Orange', 'Apple']
>>> fruits[1:]
['Orange', 'Apple', 'Banana', 'Cherry']
뿐만 아니라 범위의 시작과 끝을 모두 생략하면 리스트 전체를 복제할 수도 있고요.
>>> fruits_clone = fruits[:]
>>> fruits_clone
['Mongo', 'Orange', 'Apple', 'Banana', 'Cherry']
:
기호를 한 번 더 사용해서 원소 사이의 간격을 정할 수도 있습니다.
>>> fruits[::2]
['Mongo', 'Apple', 'Cherry']
이것을 응용하면 리스트를 거꾸로 뒤집은 복사본을 쉽게 만들 수 있습니다.
>>> fruits[::-1]
['Cherry', 'Banana', 'Apple', 'Orange', 'Mongo']
어떤가요? 정말 강력하죠? 🧙
데이터 갱신
가변 자료형인 리스트은 저장하고 있는 데이터를 자유롭게 갱신할 수 있습니다. 기존 인덱스에 새로운 값을 할당하기만 하면 기존 값이 새로운 값으로 대체됩니다.
>>> fruits[1] = 'Kiwi'
>>> fruits
['Mongo', 'Kiwi', 'Apple', 'Banana', 'Cherry']
슬라이싱 문법을 활용하여 여러 데이터를 갱신할 수도 있습니다.
>>> fruits[2:4] = ['Lemon', 'Grape']
>>> fruits
['Mongo', 'Kiwi', 'Lemon', 'Grape', 'Cherry']
데이터 삭제
리스트에서 특정 데이터를 삭제하고 싶을 때는 remove()
메서드에 해당 데이터를 인수로 넘기면 됩니다.
>>> fruits = ['Mongo', 'Kiwi', 'Apple', 'Banana', 'Cherry']
>>> fruits.remove('Kiwi')
>>> fruits
['Mongo', 'Apple', 'Banana', 'Cherry']
데이터를 리스트 내의 순서를 기준으로 삭제할 때는 pop()
메서드를 사용하는데요.
pop()
메서드에 아무 인수도 넘기지 않으면 가장 최근에 넣은 맨 뒤에 있는 원소를 삭제하고 삭제된 원소를 반환해줍니다.
>>> fruits.pop()
'Cherry'
>>> fruits
['Mongo', 'Apple', 'Banana']
pop()
메서드의 인수로 인덱스를 넘기면 해당 위치에 있는 원소를 삭제해줍니다.
>>> fruits.pop(1)
'Apple'
>>> fruits
['Mongo', 'Banana']
리스트의 insert()
메서드와 마찬가지로 pop()
메서드에 인덱스를 넘기면 성능 문제가 발생할 수 있습니다.
마찬가지로 원소이 사라진 자리를 매우기 위해서 그 뒤에 있던 기존의 원소들을 앞으로 한 칸씩 당겨야하기 때문입니다.
참고로 삭제한 데이터를 얻고 싶지 않고 그냥 삭제만 하고 싶을 때는 del
연산자를 사용할 수도 있습니다.
>>> del fruits[1]
>>> fruits
['Mongo', 'Banana']
리스트 순회
리스트에 모든 데이터를 순회하고 싶을 때는 어떻게 해야 할까요?
이 때는 for
루프문 안에서 in
연산자를 사용하면 됩니다.
>>> fruits = ['Mongo', 'Apple', 'Banana']
>>> for fruit in fruits:
... print(fruit)
...
Mongo
Apple
Banana
만약에 인덱스와 원소를 동시에 얻고 싶다면 enumerate()
함수를 활용하면 됩니다.
for idx, fruit in enumerate(fruits):
... print(idx, fruit)
...
0 Mongo
1 Apple
2 Banana
파이썬의 내장 함수인
enumerate()
에 대한 자세한 설명은 관련 포스팅을 참고 바랍니다.
데이터 존재 여부 확인
in
연산자를 사용하면 특정 데이터가 리스트에 존재하는지도 쉽게 확인 할 수 있습니다.
>>> fruits = ['Mongo', 'Apple', 'Banana']
>>> "Apple" in fruits
True
>>> "Lemon" in fruits
False
>>> "Lemon" not in fruits
True
따라서 if
조건문과도 자연스럽게 함께 사용할 수 있습니다.
>>> if "Apple" in fruits:
... print("Apple은 존재합니다.")
... else:
... print("Apple은 존재하지 않습니다.")
...
Apple은 존재합니다.
리스트 병합
여러 개의 리스트을 합쳐야할 때는 더하기(+
) 연산자를 사용하면 됩니다.
>>> li1 = ["A", "B"]
>>> li2 = ["C", "D"]
>>> li1 + li2
['A', 'B', 'C', 'D']
참고로 자바스크립트 개발자라시면 ...
연산자와 비슷한 문법도 제공합니다.
*
연산자를 사용하여, 대괄호 안에 합칠 리스트들을 쉼표(,
)로 구분하여 나열하면 됩니다.
>>> [*li1, *li2]
['A', 'B', 'C', 'D']
이 문법을 잘 활용하면 좀 더 유연하게 리스트를 생성할 수 있습니다.
>>> [1, 2, *li1, 3, 4, *li2] # [1, 2] + li1 + [3, 4] + li2
[1, 2, 'A', 'B', 3, 4, 'C', 'D']
리스트 복제
곱하기(*
) 연산자를 사용하면 같은 리스트를 여러 번 복제할 수도 있습니다.
>>> li = ["A", "B", "C"]
>>> li * 1
['A', 'B', 'C']
>>> li * 2
['A', 'B', 'C', 'A', 'B', 'C']
>>> li * 3
['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']
리스트 복제가 이보다 쉬울 수 있을까요? 😉
리스트 확장
하나의 리스트에 다른 리스트의 모든 데이터를 추가하고 싶을 때는 리스트의 extend()
메서드를 사용할 수 있습니다.
>>> li1 = ["A", "B"]
>>> li2 = ["C", "D"]
>>> li1.extend(li2)
>>> li1
['A', 'B', 'C', 'D']
바로 위에서 다룬 단순 병합과 달리 기존 리스트에 있는 데이터에 직접적인 변경이 가해지는 점에 주의 바라겠습니다.
리스트의 동등성
맨 처음에 리스트는 순서가 있게 데이터를 저장한다고 말씀드렸는데요. 그래서 동일한 데이터를 담고 있어도 그 데이터의 순서가 틀리다면 동일한 리스트로 취급되지 않습니다.
>>> ["A", "B", "C"] == ["A", "B", "C"]
True
>>> ["A", "B", "C"] == ["A", "C", "B"]
False
리스트는 같은 데이터를 중복해서도 저장할 수 있기 때문에 중복 여부가 동등성에 영향을 줄 수 있습니다.
>>> ["A", "B"] == ["A", "A", "B", "B"]
False
리스트와 같이 쓸 수 있는 내장 함수
파이썬에서는 리스트와 함께 쓸 수 있는 다양한 함수를 제공하고 있습니다.
len()
내장 함수에 리스트를 넘기면 해당 리스트의 길이가 반환되고, min()
내장 함수에 리스트를 넘기면 가장 작은 데이터가 반환되며, max()
내장 함수에 리스트를 넘기면 가장 큰 데이터가 반환됩니다.
>>> li = ["A", "B", "C"]
>>> len(li)
3
>>> min(li)
'A'
>>> max(li)
'C'
이 밖에도 filter()
나 map()
내장 함수를 리스트와 함께 사용해서 강력한 시너지를 얻을 수 있는데요.
이 부분은 본 포스팅에서 다루고자 하는 범위에서 벗어나므로 아래에 별도 포스팅으로 정리해두었습니다.
고급 활용
지금까지 배운 리스트의 문법들을 조합하면 다음과 같이 멋진 코드를 작성할 수 있습니다! 😎
>>> fruits = [[*"Mongo"], "Kiwi", *["Apple", "Banana", "Cherry"]]
>>> fruits
['Mongo', 'Kiwi', 'Apple', 'Banana', 'Cherry']
>>> fruits += ["Orange", "Grape"][::-1] * 2
['Mongo', 'Kiwi', 'Apple', 'Banana', 'Cherry', 'Orange', 'Grape', 'Orange', 'Grape']
>>> fruits[5:7] = ["Lemon", "Pear"]
>>> fruits
['Mongo', 'Kiwi', 'Apple', 'Banana', 'Cherry', 'Lemon', 'Pear', 'Grape', 'Orange']
>>> fruits[5:] = []
>>> fruits
['Mongo', 'Kiwi', 'Apple', 'Banana', 'Cherry']
위 코드가 당장 이해가 되지 않으시다라도 너무 걱정하실 필요 없습니다. 실제 프로젝트에서 이렇게 코드를 작성하면 아마 동료 개발자들이 별로 안 좋아할껄요? 😂
전체 코드
본 포스팅에서 제가 작성한 전체 코드는 아래에서 직접 확인하고 실행해보실 수 있습니다.
마치면서
이상으로 파이썬의 리스트(list) 자료형의 특징과 사용 방법에 대해서 자세히 살펴보았습니다. 이 정도만 숙지하시면 실전 프로그래밍에서에서 큰 부족함없이 리스트을 활용하실 수 있으실 거에요. 파이썬에서 정말 광범위하게 사용되는 내장 자료형인 만큼 이번 기회에 사용법을 잘 정리해두셨으면 좋을 것 같습니다.