상위 목록: 하위 목록: 작성 날짜: 읽는 데 11 분 소요

목록(List)

Python에서는 List를 이용하여 다양한 연산이 가능합니다.

데이터 형식과는 무관하게 저장할 수 있으며 List안에 또 다른 List를 포함시킬 수 있습니다.

List는 대괄호([])를 사용하며 순서를 중요시합니다. 또한 연산시 원소에 대한 값이 아닌 List 자체에 대한 연산을 실시합니다.

즉, 목록 자체에 대한 연산이므로 내부 요소가 아닌 목록에 대해 영향을 미칩니다.


제 8강에서는 목록을 만들고, 이어 붙이고, 원하는 위치의 값을 꺼내는 방법을 다루었습니다. 이번 강좌에서는 목록이 변경 가능한(Mutable) 자료형이라는 특징을 활용하여, 이미 만든 목록의 내용을 조사·변경·삽입·삭제·정렬하는 방법을 다룹니다.

이번 강좌의 함수들을 사용할 때 가장 중요한 기준은 원본을 직접 바꾸는가, 새 객체를 반환하는가입니다.

  • append(), insert(), extend(), remove(), sort(), reverse(), clear()처럼 원본을 직접 바꾸는 메서드는 결과를 따로 돌려주지 않고 None을 반환합니다.
  • pop()은 원본을 바꾸면서 꺼낸 원소를 반환합니다.
  • 슬라이싱, sorted(), reversed()는 원본을 그대로 두고 새 객체를 반환합니다.


a = [1, 2, 3]

result = a.append(4)
print(result)
print(a)

a = a.append(5)
print(a)
결과
None
[1, 2, 3, 4]
None

a.append(4)는 a에 4를 추가하지만, 메서드 자체의 반환값은 None입니다. 그래서 result에는 None이 저장되고, 변경된 내용은 a를 출력해야 확인할 수 있습니다.

초보자가 가장 많이 하는 실수가 마지막 예제처럼 a = a.append(5)라고 작성하는 것입니다. append()가 반환한 None이 a에 다시 저장되므로 목록 자체를 잃어버립니다.

원본을 바꾸는 메서드는 a.append(5)처럼 단독으로 호출하고, 결과를 변수에 대입하지 않습니다.



조사

목록을 바꾸기 전에 먼저 목록이 어떤 상태인지 알아야 하는 경우가 많습니다. 길이, 가장 작은 값과 큰 값, 특정 값의 위치와 개수, 존재 여부는 목록을 변경하지 않고 정보만 알려 주는 연산입니다.

a = [1, 1, 2, 3, 5, 8, 13]
b = ["a", "a", "b", "c", "xyz", [1, 2, 3]]

print(len(a))
print(min(a))
print(max(a))
print(b.index([1, 2, 3]))
print(b.count("a"))
print("a" in b)
결과
7
1
13
5
2
True

len()을 이용하여 목록의 길이를 확인할 수 있습니다.

min()을 이용하여 목록에서 최솟값을 가지는 원소의 값을 확인할 수 있습니다.

max()를 이용하여 목록에서 최댓값을 가지는 원소의 값을 확인할 수 있습니다.

목록.index()를 이용하여 해당 값이 가지는 위치를 확인 할 수 있습니다. (시작값=0)

목록.count()를 이용하여 해당 값이 가지는 개수를 확인할 수 있습니다.

값 in 목록를 이용하여 해당 값이 존재하는지 확인할 수 있습니다.

목록.index()와 목록.remove()는 값이 목록에 없으면 ValueError가 발생하므로, 확실하지 않을 때는 in으로 먼저 확인합니다.


결과를 차례대로 읽으면, a는 원소가 7개이고 가장 작은 값은 1, 가장 큰 값은 13입니다. b에서 목록 [1, 2, 3]은 5번 위치에 있고, "a"는 2개 들어 있으며, "a" in b는 True입니다.

len(), min(), max()는 목록 전용 메서드가 아니라 내장 함수이므로 len(목록) 형태로 호출하고, index(), count()는 목록의 메서드이므로 목록.index() 형태로 호출합니다.

in, index(), count()는 목록의 처음부터 끝까지 원소를 하나씩 비교하여 값을 찾습니다. 목록은 집합·사전처럼 해시로 위치를 계산하지 않기 때문에, 목록이 길어질수록 찾는 시간도 길이에 비례해서 늘어납니다. 값의 존재 여부를 매우 자주 확인해야 한다면 제 13강의 집합(Set)을 사용하는 것이 좋습니다.


a = ["a", "b", "a", "c", "a"]

print(a.index("a"))
print(a.index("a", 1))
print(a.index("a", 3))

try:
    a.index("z")
except ValueError as e:
    print(e)
결과
0
2
4
list.index(x): x not in list

목록.index(value)는 같은 값이 여러 개 있으면 가장 앞에 있는 위치 하나만 반환합니다.

목록.index(value, start)처럼 두 번째 인수를 전달하면 start 위치부터 찾습니다. a.index("a", 1)은 1번 위치부터 찾으므로 2번 위치의 "a"를 찾고, a.index("a", 3)은 4번 위치의 "a"를 찾습니다.

값이 없을 때 발생하는 ValueError의 메시지는 list.index(x): x not in list입니다.


print(max(["apple", "banana", "cherry"]))
print(min([[1, 2], [1, 3], [0, 9]]))

try:
    max([1, "a"])
except TypeError as e:
    print(e)
결과
cherry
[0, 9]
‘>’ not supported between instances of ‘str’ and ‘int’

max()와 min()은 원소끼리 크기를 비교할 수 있으면 사용할 수 있습니다.

문자열끼리는 사전 순서로, 목록끼리는 앞쪽 원소부터 차례대로 비교합니다.

[[1, 2], [1, 3], [0, 9]]에서는 각 목록의 첫 원소 1, 1, 0을 먼저 비교하므로, 두 번째 원소가 9로 크더라도 [0, 9]가 가장 작은 값이 됩니다.

  • Tip : 숫자와 문자열처럼 서로 비교할 수 없는 자료형이 섞인 목록에서는 TypeError가 발생합니다.



대입

목록은 변경 가능한 자료형이므로, 이미 들어 있는 값을 다른 값으로 바로 바꿔 쓸 수 있습니다. 문자열이나 튜플에서는 불가능한, 목록만의 특징입니다.

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

a[1] = 2

print(a)
결과
[0, 2, 2, 3, 4, 5, 6, 7, 8, 9]

목록의 값을 직접적으로 변경시켜 값을 변경할 수 있습니다.

a[1] = 2는 1번 위치가 가리키던 값 1 대신 2를 가리키도록 바꿉니다. 새 목록을 만드는 것이 아니라 원본 목록의 칸을 바꾸는 것이므로, 같은 목록을 가리키는 다른 변수에도 변경이 보입니다.


a = [0, 1, 2, 3, 4, 5]

a[1:3] = [10, 20, 30]
print(a)

a[0:2] = []
print(a)

try:
    a[100] = 1
except IndexError as e:
    print(e)
결과
[0, 10, 20, 30, 3, 4, 5]
[20, 30, 3, 4, 5]
list assignment index out of range

목록[start:end] = 목록처럼 슬라이스에 대입하면 구간 전체를 다른 원소들로 교체합니다.

첫 줄에서 a[1:3]은 원소 1, 2 두 개를 가리키며, 여기에 원소 세 개 [10, 20, 30]을 대입했습니다. 구간의 길이와 대입하는 원소의 개수가 달라도 되므로 목록의 길이가 6에서 7로 늘어났습니다.

a[0:2] = []처럼 빈 목록을 대입하면 해당 구간이 삭제됩니다.

반면 a[100] = 1처럼 존재하지 않는 위치에 대입하면 IndexError가 발생합니다. 인덱스 대입은 이미 있는 칸을 바꾸는 것이지 새 칸을 만드는 것이 아니므로, 값을 추가할 때는 다음 절의 append()나 insert()를 사용합니다.



삽입

목록에 새 원소를 추가하는 방법은 세 가지입니다. 원하는 위치에 하나를 넣는 insert(), 맨 뒤에 하나를 넣는 append(), 맨 뒤에 여러 개를 한꺼번에 넣는 extend()입니다. 세 메서드 모두 원본을 직접 변경하고 None을 반환합니다.

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

a.insert(0, -1)
a.insert(-1, 10)

print(a)
결과
[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 9]

목록.insert(index, value)를 이용하여 index의 바로 앞자리에 value를 삽입합니다.

a.insert(0, -1)은 0번 위치 앞, 즉 맨 앞에 -1을 넣습니다. a.insert(-1, 10)은 마지막 원소 9의 앞에 10을 넣으므로, 10이 맨 끝이 아니라 끝에서 두 번째에 들어갑니다.

insert()는 새 원소가 들어갈 자리를 만들기 위해 그 뒤의 원소들을 한 칸씩 뒤로 밀어야 합니다. 그래서 맨 앞에 삽입할수록, 목록이 길수록 시간이 오래 걸립니다.


a = [1, 2, 3]

a.insert(100, 4)
a.insert(-100, 0)

print(a)
결과
[0, 1, 2, 3, 4]

insert()의 index가 목록의 범위를 벗어나도 오류가 발생하지 않습니다. 너무 큰 값은 맨 뒤, 너무 작은 값은 맨 앞으로 간주합니다.


a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

a.append(10)

print(a)
결과
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

목록.append(value)를 이용하여 목록의 마지막 자리에 value를 삽입합니다.

append()는 뒤에 붙이기만 하므로 다른 원소를 옮길 필요가 없습니다. Python의 목록은 뒤쪽에 여유 공간을 미리 확보해 두기 때문에, append()는 목록의 길이와 상관없이 평균적으로 일정한 시간에 끝납니다. 값을 하나씩 모아 목록을 만들 때 append()를 가장 많이 사용하는 이유입니다.


a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [10, 11]

a.extend(b)

print(a)
결과
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

목록.extend(list)를 이용하여 목록의 마지막 자리에 list의 원소들을 삽입합니다.

a.extend(b)는 a를 직접 변경하며, b는 그대로 남습니다. 8강에서 다룬 a += b와 같은 동작입니다.


a = [1, 2]
b = [1, 2]
c = [1, 2]

a.append([3, 4])
b.extend([3, 4])
c.extend("ab")

print(a)
print(b)
print(c)
print(len(a), len(b))
결과
[1, 2, [3, 4]]
[1, 2, 3, 4]
[1, 2, ‘a’, ‘b’]
3 4

append()와 extend()는 목록을 인수로 줄 때 결과가 완전히 다릅니다.

append([3, 4])는 목록 [3, 4]를 원소 하나로 통째로 추가하므로 a의 길이는 3이 됩니다. extend([3, 4])는 [3, 4]의 원소를 하나씩 꺼내 추가하므로 b의 길이는 4가 됩니다.

extend()는 원소를 꺼낼 수 있는 객체라면 무엇이든 받으므로, c.extend("ab")는 문자열 "ab"가 아니라 'a', 'b' 두 문자를 추가합니다.

  • Tip : 값 하나를 추가할 때는 append(), 다른 목록의 원소들을 이어 붙일 때는 extend()를 사용합니다.



삭제

원소를 삭제하는 방법도 목적에 따라 나뉩니다. 위치를 알고 있으면 del이나 pop()을, 값을 알고 있으면 remove()를 사용하며, 모든 원소를 지우려면 clear()를 사용합니다.

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

del a[1:-1]

print(a)
결과
[0, 9]

del 목록[start:end]를 이용하여 start부터 end-1까지의 원소를 삭제합니다.

del 목록[index]처럼 하나의 index만 지정하면 해당 원소 하나만 삭제합니다.

예제의 a[1:-1]은 첫 원소와 마지막 원소를 제외한 구간이므로, 0과 9만 남습니다.

del은 메서드가 아닌 Python의 문장(Statement)이므로 반환값이 없으며, del a처럼 변수 이름 자체를 지우는 데에도 사용합니다.


a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

a.pop(1)

print(a)
결과
[0, 2, 3, 4, 5, 6, 7, 8, 9]

목록.pop(index)를 이용하여 index에 위치한 원소를 삭제합니다.

  • Tip : index를 생략할 경우 index의 값을 -1로 간주합니다.


a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

x = a.pop(1)
y = a.pop()

print(x, y)
print(a)
결과
1 9
[0, 2, 3, 4, 5, 6, 7, 8]

목록.pop()은 삭제한 원소를 반환하므로, 꺼낸 값을 변수에 저장해 사용할 수 있습니다.

a.pop(1)은 1번 위치의 1을 꺼내 x에 저장하고, a.pop()은 마지막 원소 9를 꺼내 y에 저장합니다. 두 원소가 빠졌으므로 a의 길이는 8이 됩니다.

pop()으로 마지막 원소를 꺼내는 것은 뒤의 원소를 옮길 필요가 없어 빠르지만, pop(0)처럼 앞쪽 원소를 꺼내면 뒤의 원소들을 모두 한 칸씩 앞으로 당겨야 하므로 목록이 길수록 느려집니다.

  • Tip : 목록 끝에서 넣고 빼는 append()와 pop()을 함께 사용하면 마지막에 넣은 값을 먼저 꺼내는 스택(Stack)을 만들 수 있습니다.


a = []

try:
    a.pop()
except IndexError as e:
    print(e)
결과
pop from empty list

빈 목록에서 pop()을 호출하면 꺼낼 원소가 없으므로 IndexError가 발생합니다. 반복문에서 pop()을 사용할 때는 목록이 비어 있지 않은지 먼저 확인합니다.


a = ["x", "y", "z", "y"]

a.remove("y")

print(a)
결과
[‘x’, ‘z’, ‘y’]

목록.remove(value)를 이용하여 value와 동일한 값을 지니는 원소 하나를 삭제합니다.

  • Tip : index가 0에 가까운 순서부터 삭제합니다.

  • Tip : 목록에 없는 값을 remove()하면 ValueError가 발생합니다.

remove()는 값이 어디에 있는지 모르기 때문에 앞에서부터 하나씩 비교하여 처음 일치하는 원소를 찾고, 그 원소를 지운 뒤 뒤의 원소들을 앞으로 당깁니다. 반환값은 None입니다.


a = [1, 2, 1, 3, 1]

b = [x for x in a if x != 1]
print(b)

a.clear()
print(a)
결과
[2, 3]
[]

remove()는 한 번에 하나만 지우므로, 특정 값을 모두 지우고 싶다면 목록 내포로 그 값을 제외한 새 목록을 만드는 방법이 간단합니다. b에는 1이 아닌 원소 2, 3만 남습니다.

목록.clear()는 목록의 모든 원소를 삭제하여 빈 목록으로 만듭니다. a = []는 a가 새 빈 목록을 가리키게 할 뿐 원래 목록은 그대로 남지만, clear()는 원래 목록 자체를 비우므로 같은 목록을 가리키는 다른 변수에서도 빈 목록이 보입니다.



자르기

목록의 일부만 남기고 싶을 때는 8강에서 배운 슬라이싱을 사용합니다. 슬라이싱은 새 목록을 반환하므로, 원본을 바꾸려면 결과를 다시 대입해야 합니다.

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

a = a[:4]

print(a)
print(a[1:])
print(a)
결과
[0, 1, 2, 3]
[1, 2, 3]
[0, 1, 2, 3]

a[start:end]를 이용하여 목록을 자를 수 있습니다. start와 end를 미입력시 각각 첫 번째 index와 마지막 index로 간주합니다.

a = a[:4]는 앞의 네 원소로 새 목록을 만들어 a에 다시 대입했으므로 a가 [0, 1, 2, 3]이 됩니다.

반면 print(a[1:])는 잘라 낸 새 목록을 출력만 하고 버리므로, 바로 다음 줄에서 a를 출력하면 여전히 [0, 1, 2, 3]입니다.

  • Tip : 대입 연산을 하지 않으면 원본 목록에는 영향을 주지 않습니다.



정렬

목록의 원소를 크기 순서로 늘어놓는 것을 정렬(Sort)이라 합니다. 정렬도 원본을 바꾸는 방법(목록.sort())과 새 목록을 만드는 방법(sorted())이 있으며, 둘 중 어느 것을 쓰는지에 따라 코드 작성 방법이 달라집니다.

a = [1, 0, 2, 4, 3]

a.sort()

print(a)
결과
[0, 1, 2, 3, 4]

목록.sort()를 이용하여 목록을 오름차순으로 정렬합니다.

sort()는 원본 목록 a의 순서를 직접 바꾸고 None을 반환합니다. 정렬은 원소끼리 비교하여 순서를 정하므로, 원소의 개수가 n일 때 대략 n log n에 비례하는 시간이 걸립니다.


a = [1, 0, 2, 4, 3]

a.sort(reverse=True)

print(a)
결과
[4, 3, 2, 1, 0]

목록.sort(reverse=True)를 이용하여 목록을 내림차순으로 정렬합니다.

  • Tip : reverse=False로 사용할 경우 오름차순으로 정렬합니다.


a = [1, 0, 2, 4, 3]

a.reverse()

print(a)
결과
[3, 4, 2, 0, 1]

목록.reverse()를 이용하여 목록의 순서를 역순으로 뒤집습니다.

원래 목록 [1, 0, 2, 4, 3]을 뒤에서부터 읽으면 [3, 4, 2, 0, 1]이 됩니다. 값을 비교하지 않고 위치만 뒤집습니다.

  • Tip : reverse()는 값의 크기와 상관없이 현재 순서만 뒤집으므로 내림차순 정렬과는 다릅니다. 원본을 유지하려면 목록[::-1]을 사용합니다.


a = [1, 0, 2, 4, 3]

print(list(reversed(a)))
print(a)
결과
[3, 4, 2, 0, 1]
[1, 0, 2, 4, 3]

내장 함수 reversed(목록)도 역순으로 원소를 돌려주지만, 원본을 바꾸지 않습니다.

reversed()는 목록이 아닌 역순으로 원소를 하나씩 꺼내 주는 객체를 반환하므로, 목록이 필요하면 list()로 변환합니다. 반복문에서 거꾸로 순회할 때는 변환 없이 for x in reversed(a):처럼 바로 사용할 수 있습니다.


a = [1, 0, 2, 4, 3]

a = sorted(a, reverse=True)

print(a)
결과
[4, 3, 2, 1, 0]

sorted(목록, reverse=bool)를 이용하여 목록을 내림차순으로 정렬합니다.

목록.sort()는 원본 목록을 직접 변경하고 None을 반환하지만, sorted()는 원본을 그대로 두고 정렬된 새 목록을 반환합니다.


a = [3, 1, 2]
a = a.sort()
print(a)

print(sorted((3, 1, 2)))
print(sorted("cab"))
결과
None
[1, 2, 3]
[‘a’, ‘b’, ‘c’]

a = a.sort()는 append()에서 본 것과 같은 실수입니다. sort()의 반환값 None이 a에 대입되어 목록을 잃어버립니다. 정렬된 결과를 다른 변수에 받고 싶다면 sorted()를 사용합니다.

또한 sort()는 목록에만 있는 메서드지만, sorted()는 튜플이나 문자열처럼 원소를 꺼낼 수 있는 객체라면 무엇이든 정렬하여 항상 목록으로 반환합니다.

  • Tip : 원본의 순서가 더 이상 필요 없으면 sort(), 원본을 보존해야 하거나 목록이 아닌 객체를 정렬할 때는 sorted()를 사용합니다.


words = ["banana", "kiwi", "apple", "fig"]

print(sorted(words, key=len))
print(words)

words.sort(key=len, reverse=True)
print(words)
결과
[‘fig’, ‘kiwi’, ‘apple’, ‘banana’]
[‘banana’, ‘kiwi’, ‘apple’, ‘fig’]
[‘banana’, ‘apple’, ‘kiwi’, ‘fig’]

key 매개변수에 함수를 전달하면, 원소 자체가 아닌 함수의 반환값을 기준으로 정렬합니다.

예제에서는 len을 전달하여 문자열의 길이를 기준으로 정렬했습니다.

기준값이 같은 원소들은 원래의 순서를 유지합니다. (안정 정렬)

sorted(words, key=len)은 각 단어의 길이 6, 4, 5, 3을 기준으로 정렬한 새 목록을 반환하며, words 자체는 바뀌지 않습니다. 이어서 words.sort(key=len, reverse=True)는 길이가 긴 순서로 원본을 정렬합니다.


print(sorted(["b", "A", "a", "B"]))
print(sorted(["b", "A", "a", "B"], key=str.lower))

try:
    [3, "a", 1].sort()
except TypeError as e:
    print(e)
결과
[‘A’, ‘B’, ‘a’, ‘b’]
[‘A’, ‘a’, ‘b’, ‘B’]
‘<’ not supported between instances of ‘str’ and ‘int’

문자열은 문자 코드 값을 기준으로 비교하며, 대문자(A~Z)가 소문자(a~z)보다 코드 값이 작습니다. 그래서 기본 정렬에서는 대문자가 모두 소문자보다 앞에 옵니다.

대소문자를 구분하지 않고 정렬하려면 key=str.lower를 전달하여 모두 소문자로 바꾼 값을 기준으로 비교합니다. 원소 자체가 바뀌는 것은 아니므로 결과에는 원래 문자가 그대로 남습니다.

숫자와 문자열이 섞인 목록은 서로 크기를 비교할 수 없으므로 TypeError가 발생합니다.

sort()와 sorted()는 key 매개변수에 lambda를 이용해 더 세부적인 정렬을 할 수 있습니다.



정리

이름 설명 반환값/특징
len(목록) 원소의 개수 정수
min(목록), max(목록) 최솟값, 최댓값 원소, 비교 불가하면 TypeError
목록.index(x) x가 처음 나오는 위치 정수, 없으면 ValueError
목록.count(x) x의 개수 정수
x in 목록 x의 존재 여부 논리값, 앞에서부터 차례로 비교
목록[i] = x i번 원소 변경 원본 변경
목록.insert(i, x) i번 위치 앞에 삽입 None, 뒤 원소를 밀어냄
목록.append(x) 맨 뒤에 하나 추가 None, 빠름
목록.extend(x) 맨 뒤에 여러 원소 추가 None
del 목록[i] 위치로 삭제 문장(반환값 없음)
목록.pop(i) 위치로 꺼내며 삭제 꺼낸 원소, 빈 목록이면 IndexError
목록.remove(x) 값으로 첫 원소 하나 삭제 None, 없으면 ValueError
목록.clear() 모든 원소 삭제 None
목록.sort() 원본 정렬 None
sorted(x) 정렬된 새 목록 새 목록, 원본 유지
목록.reverse() 원본 순서 뒤집기 None
reversed(x) 역순 순회 객체 원본 유지

목록을 다루는 메서드는 대부분 원본을 직접 바꾸고 None을 반환합니다. 결과를 대입하지 말고 호출만 하며, 원본을 지켜야 할 때는 슬라이싱이나 sorted()처럼 새 객체를 돌려주는 방법을 사용합니다.

목록은 맨 뒤에서 추가·삭제하는 작업이 빠르고, 앞쪽에 삽입·삭제하거나 값으로 찾는 작업(in, index(), remove())은 목록이 길수록 느려진다는 점도 기억해 둡니다.

댓글 남기기