[Python] 리스트(List) 정렬 완벽정리: sort() vs sorted() 핵심 차이와 고급 활용법 (key, lambda)

파이썬에서 데이터를 다루다 보면 리스트(List)를 특정 순서로 정렬해야 하는 경우가 많습니다. 숫자 데이터를 오름차순이나 내림차순으로 정렬하거나, 문자열을 사전 순으로, 혹은 특정 기준에 따라 복잡하게 정렬해야 할 때도 있죠. 파이썬은 이러한 리스트 정렬을 위해 강력하고 편리한 두 가지 방법, sort() 메서드와 sorted() 내장 함수를 제공합니다.

이 글에서는 sort()sorted()의 기본적인 사용법부터 key 매개변수를 활용한 고급 정렬까지, 다양한 예제를 통해 알아보겠습니다.

1. sort()sorted(): 무엇이 다를까요?

본격적인 정렬 방법에 앞서, 두 가지 핵심적인 차이점을 짚고 넘어가겠습니다.

  • list.sort() 메서드:
    • 원본 리스트를 직접 수정 (in-place sorting)합니다.
    • 반환 값은 None입니다. 따라서 new_list = my_list.sort()와 같이 사용하면 new_list에는 아무것도 할당되지 않습니다.
    • 리스트 객체에만 사용할 수 있습니다.
  • sorted() 내장 함수:
    • 새로운 정렬된 리스트를 반환합니다. 원본 리스트는 변경되지 않습니다.
    • 리스트뿐만 아니라 튜플, 문자열, 딕셔너리 등 반복 가능한(iterable) 모든 객체에 사용할 수 있으며, 결과는 항상 새로운 리스트입니다.

이 차이점을 이해하는 것이 상황에 맞는 함수를 선택하는 데 매우 중요합니다.

2. 기본 정렬: 오름차순과 내림차순

가장 기본적인 정렬 방식은 오름차순(작은 값에서 큰 값 순)과 내림차순(큰 값에서 작은 값 순)입니다.

2.1. 오름차순 정렬 (Ascending Sort)

sort()sorted()는 기본적으로 오름차순으로 정렬합니다.

# 숫자 리스트 오름차순 정렬
numbers = [4, 1, 7, 3, 9]
numbers_copy = numbers[:] # sorted()와 비교하기 위한 복사본

# 1. sort() 사용: 원본 리스트 변경
numbers.sort()
print(f"sort() 후 numbers: {numbers}") # 출력: sort() 후 numbers: [1, 3, 4, 7, 9]

# 2. sorted() 사용: 새로운 리스트 반환
sorted_numbers = sorted(numbers_copy)
print(f"sorted() 후 numbers_copy (원본): {numbers_copy}") # 출력: sorted() 후 numbers_copy (원본): [4, 1, 7, 3, 9]
print(f"sorted() 결과 sorted_numbers: {sorted_numbers}") # 출력: sorted() 결과 sorted_numbers: [1, 3, 4, 7, 9]

# 문자열 리스트 오름차순 정렬 (사전 순)
fruits = ['banana', 'apple', 'cherry', 'date']
fruits_copy = fruits[:]

fruits.sort()
print(f"sort() 후 fruits: {fruits}") # 출력: sort() 후 fruits: ['apple', 'banana', 'cherry', 'date']

sorted_fruits = sorted(fruits_copy)
print(f"sorted() 결과 sorted_fruits: {sorted_fruits}") # 출력: sorted() 결과 sorted_fruits: ['apple', 'banana', 'cherry', 'date']

2.2. 내림차순 정렬 (Descending Sort)

내림차순으로 정렬하려면 reverse=True 옵션을 사용합니다.

# 숫자 리스트 내림차순 정렬
numbers = [4, 1, 7, 3, 9]
numbers_copy = numbers[:]

# 1. sort() 사용
numbers.sort(reverse=True)
print(f"sort(reverse=True) 후 numbers: {numbers}") # 출력: sort(reverse=True) 후 numbers: [9, 7, 4, 3, 1]

# 2. sorted() 사용
sorted_numbers_desc = sorted(numbers_copy, reverse=True)
print(f"sorted(reverse=True) 결과 sorted_numbers_desc: {sorted_numbers_desc}") # 출력: sorted(reverse=True) 결과 sorted_numbers_desc: [9, 7, 4, 3, 1]

# 문자열 리스트 내림차순 정렬
fruits = ['banana', 'apple', 'cherry', 'date']
fruits_copy = fruits[:]

fruits.sort(reverse=True)
print(f"sort(reverse=True) 후 fruits: {fruits}") # 출력: sort(reverse=True) 후 fruits: ['date', 'cherry', 'banana', 'apple']

sorted_fruits_desc = sorted(fruits_copy, reverse=True)
print(f"sorted(reverse=True) 결과 sorted_fruits_desc: {sorted_fruits_desc}") # 출력: sorted(reverse=True) 결과 sorted_fruits_desc: ['date', 'cherry', 'banana', 'apple']

3. 고급 정렬: key 매개변수 활용하기

단순한 오름차순/내림차순을 넘어, 특정 기준에 따라 리스트를 정렬하고 싶을 때는 key 매개변수를 사용합니다. key 매개변수에는 각 리스트 요소에 대해 호출될 함수를 지정하며, 이 함수의 반환 값을 기준으로 정렬이 이루어집니다.

3.1. 문자열 길이로 정렬하기

문자열의 내용이 아닌, 길이를 기준으로 정렬할 수 있습니다. len() 내장 함수를 key로 사용합니다.

words = ['python', 'is', 'awesome', 'really']

# 길이 오름차순
words.sort(key=len)
print(f"길이 오름차순 (sort): {words}") # 출력: 길이 오름차순 (sort): ['is', 'really', 'python', 'awesome']

words2 = ['python', 'is', 'awesome', 'really']
sorted_by_length = sorted(words2, key=len)
print(f"길이 오름차순 (sorted): {sorted_by_length}") # 출력: 길이 오름차순 (sorted): ['is', 'really', 'python', 'awesome']

# 길이 내림차순
words.sort(key=len, reverse=True)
print(f"길이 내림차순 (sort): {words}") # 출력: 길이 내림차순 (sort): ['awesome', 'python', 'really', 'is']

3.2. 대소문자 구분 없이 문자열 정렬하기

기본 문자열 정렬은 대문자가 소문자보다 앞에 옵니다. 대소문자를 구분하지 않고 정렬하려면 str.lower (또는 str.upper) 메서드를 key로 사용합니다.

names = ['Alice', 'bob', 'Charlie', 'david']

# 기본 정렬 (대문자 우선)
names_copy1 = names[:]
names_copy1.sort()
print(f"기본 정렬: {names_copy1}") # 출력: 기본 정렬: ['Alice', 'Charlie', 'bob', 'david']

# 대소문자 구분 없이 정렬
names_copy2 = names[:]
names_copy2.sort(key=str.lower)
print(f"대소문자 무시 정렬: {names_copy2}") # 출력: 대소문자 무시 정렬: ['Alice', 'bob', 'Charlie', 'david']

3.3 복잡한 데이터 구조 정렬 (튜플, 리스트의 특정 요소 기준)

리스트의 요소가 튜플이나 다른 리스트(2차원 리스트 등)일 경우, 내부 요소 중 특정 인덱스의 값을 기준으로 정렬할 수 있습니다. 이때 lambda 함수가 유용하게 사용됩니다.

예시 1: 튜플 리스트에서 두 번째 요소를 기준으로 정렬

records = [('Alice', 30), ('Bob', 25), ('Charlie', 35)]

# 나이(두 번째 요소, 인덱스 1) 기준 오름차순 정렬
records.sort(key=lambda x: x[1])
print(f"나이 오름차순: {records}")
# 출력: 나이 오름차순: [('Bob', 25), ('Alice', 30), ('Charlie', 35)]

# 나이(두 번째 요소, 인덱스 1) 기준 내림차순 정렬
records.sort(key=lambda x: x[1], reverse=True)
print(f"나이 내림차순: {records}")
# 출력: 나이 내림차순: [('Charlie', 35), ('Alice', 30), ('Bob', 25)]

예시 2: 2차원 리스트에서 특정 열을 기준으로 정렬 (숫자형 데이터)

숫자형 데이터를 key로 사용하여 내림차순 정렬할 때, lambda x: -x[index] 트릭을 사용할 수 있습니다. (단, reverse=True를 사용하는 것이 더 명시적입니다.)

data = [['apple', 3, 100], ['banana', 1, 200], ['cherry', 2, 50]]

# 두 번째 요소(인덱스 1) 기준 내림차순, 세 번째 요소(인덱스 2) 기준 오름차순
# lambda 함수의 반환 값으로 튜플을 사용하면, 첫 번째 요소로 정렬 후 동일 값에 대해 두 번째 요소로 정렬
data.sort(key=lambda x: (-x[1], x[2])) # -x[1]로 내림차순 효과, x[2]로 오름차순
print(f"복합 조건 정렬: {data}")
# 출력: 복합 조건 정렬: [['apple', 3, 100], ['cherry', 2, 50], ['banana', 1, 200]]

# 위와 동일한 결과를 reverse 옵션과 함께 명시적으로 표현
data2 = [['apple', 3, 100], ['banana', 1, 200], ['cherry', 2, 50]]
# 먼저 세 번째 요소(x[2])로 오름차순 정렬
data2.sort(key=lambda x: x[2])
# 그 다음 두 번째 요소(x[1])로 내림차순 정렬 (안정 정렬 특성 활용)
data2.sort(key=lambda x: x[1], reverse=True)
print(f"복합 조건 정렬 (단계별): {data2}")
# 출력: 복합 조건 정렬 (단계별): [['apple', 3, 100], ['cherry', 2, 50], ['banana', 1, 200]]
# 이 방법은 여러 번 sort를 호출해야 하지만, lambda 튜플이 더 간결합니다.

위의 lambda x: (-x[1], x[2]))는 파이썬의 정렬 안정성(stable sort)과 튜플 비교 방식을 활용한 것입니다. 튜플은 첫 번째 요소부터 순차적으로 비교하여 정렬 순서를 결정합니다. -x[1]x[1]의 부호를 바꿔 내림차순 효과를 내고, x[1] 값이 같다면 x[2]의 오름차순으로 정렬합니다.

4. sort() vs sorted(): 언제 무엇을 써야 할까?

  • 원본 리스트를 유지하고, 정렬된 새 리스트가 필요할 때: sorted()를 사용하세요. 특히, 튜플이나 다른 반복 가능한 객체를 정렬하여 리스트로 받고 싶을 때 유용합니다.
  • 원본 리스트 자체를 정렬된 상태로 변경하고 싶을 때: list.sort()를 사용하세요. 메모리 사용 측면에서 약간 더 효율적일 수 있습니다 (새 리스트를 만들지 않으므로).
  • 함수의 반환 값으로 정렬된 리스트를 바로 사용해야 할 때: sorted()가 더 편리합니다.
my_list = [5, 2, 8, 1, 9]

# 원본은 그대로 두고, 정렬된 새 리스트 얻기
new_sorted_list = sorted(my_list)
print(f"원본 my_list: {my_list}")         # 출력: 원본 my_list: [5, 2, 8, 1, 9]
print(f"새 리스트 new_sorted_list: {new_sorted_list}") # 출력: 새 리스트 new_sorted_list: [1, 2, 5, 8, 9]

# 원본 리스트 자체를 변경하기
my_list.sort()
print(f"정렬된 my_list: {my_list}")       # 출력: 정렬된 my_list: [1, 2, 5, 8, 9]

마무리

파이썬의 sort() 메서드와 sorted() 내장 함수는 리스트 정렬을 위한 강력한 도구입니다. 기본 오름차순/내림차순 정렬뿐만 아니라, key 매개변수와 lambda 함수를 조합하여 거의 모든 원하는 순서대로 데이터를 정렬할 수 있습니다. 이 두 가지 방법의 차이점을 명확히 이해하고, 상황에 맞게 적절히 활용한다면 파이썬 프로그래밍의 효율성을 크게 높일 수 있을 것입니다.

참고로, 리스트의 순서를 단순히 뒤집고 싶을 때는 list.reverse() 메서드나 reversed() 내장 함수 (이터레이터 반환)를 사용할 수 있습니다. 이는 정렬과는 다른 기능이니 혼동하지 마세요!