Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

파이썬 스타 표현식(*)으로 언패킹하기 – Catch-all 언패킹 완벽 가이드

들어가며

파이썬에서 시퀀스를 언패킹(unpacking)할 때 기본적으로 가지는 제약 중 하나는, 언패킹하려는 시퀀스의 길이를 미리 정확히 알고 있어야 한다는 점입니다. 하지만 실무에서는 길이를 미리 알 수 없거나, 일부 값만 필요한 경우가 많습니다.

이런 상황에서 유용하게 쓸 수 있는 것이 바로 스타 표현식(starred expression)을 활용한 catch-all 언패킹입니다. 이 글에서는 문제 상황부터 해결 방법, 그리고 반드시 알아야 할 주의사항까지 예제와 함께 살펴보겠습니다.

문제 상황: 길이를 모르면 언패킹에 실패한다

먼저 내림차순으로 정렬된 숫자 리스트를 준비해 보겠습니다.

random_numbers = [0, 1, 5, 9, 17, 12, 7, 10, 3, 2]
random_numbers_descending = sorted(random_numbers, reverse=True)
print(f"Output \n*** {random_numbers_descending}")

실행 결과

*** [17, 12, 10, 9, 7, 5, 3, 2, 1, 0]

여기서 '최댓값과 두 번째로 큰 값'만 얻고 싶다고 가정해 봅시다. 단순히 변수 두 개로 언패킹하려고 하면 어떻게 될까요?

print(f"Output \n*** Getting the largest and second largest")
largest, second_largest = random_numbers_descending

실행 결과

*** Getting the largest and second largest
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
----> 2 largest, second_largest = random_numbers_descending

ValueError: too many values to unpack (expected 2)

리스트에는 10개의 요소가 있는데 변수는 2개뿐이므로, 위처럼 ValueError: too many values to unpack 예외가 발생합니다.

전통적인 해결법: 인덱싱과 슬라이싱

파이썬에서는 전통적으로 인덱싱과 슬라이싱으로 이 문제를 처리해 왔습니다.

largest = random_numbers_descending[0]
second_largest = random_numbers_descending[1]
rest_of_numbers = random_numbers_descending[2:]

실행 결과

*** Getting the largest - 17
*** Getting the second largest - 12
*** Getting the rest of numbers - [10, 9, 7, 5, 3, 2, 1, 0]

동작은 하지만, 이렇게 인덱스와 슬라이스를 남발하면 코드가 지저분해지고 읽기 어려워집니다. 특히 시퀀스의 요소들을 여러 개의 부분집합으로 나눌 때는 인덱스를 잘못 지정해 버그가 발생하기 쉽습니다(off-by-one 오류 등).

더 나은 해결책: 스타 표현식을 활용한 Catch-all 언패킹

파이썬은 이를 더 우아하게 처리할 수 있도록 별표(*)를 사용한 catch-all 언패킹을 지원합니다. 스타 표현식을 사용하면 언패킹 패턴의 다른 부분과 매칭되지 않는 나머지 모든 값을 한 변수가 받게 됩니다.

largest, second_largest, *rest_of_numbers = random_numbers_descending
print(f"Output \n largest: {largest} \n second_largest:{second_largest} \n rest_of_numbers:{rest_of_numbers}")

실행 결과

largest: 17
second_largest: 12
rest_of_numbers: [10, 9, 7, 5, 3, 2, 1, 0]

어떤가요? 한 줄의 코드로도 훨씬 읽기 쉽게 동일한 결과를 얻었습니다.

스타 표현식은 어느 위치에나 올 수 있다

스타 표현식은 언패킹 대입문의 어느 위치에든 놓일 수 있습니다. 덕분에 원하는 슬라이스를 추출해야 하는 모든 상황에서 catch-all 언패킹의 장점을 누릴 수 있습니다.

예를 들어 최솟값 쪽을 앞세우고 싶다면 다음과 같이 작성합니다.

*rest_of_numbers, second_smallest, smallest = random_numbers_descending
print(f"Output \n rest_of_numbers:{rest_of_numbers} \n second_smallest: {second_smallest} \n smallest:{smallest}")

실행 결과

rest_of_numbers: [17, 12, 10, 9, 7, 5, 3, 2]
second_smallest: 1
smallest: 0

반드시 알아야 할 주의사항

1. 필수 요소는 하나 이상 있어야 한다

스타 표현식이 포함된 언패킹 대입문에는 반드시 필수(required) 부분이 하나 이상 있어야 합니다. 그렇지 않으면 SyntaxError가 발생합니다. 즉, catch-all 표현식을 단독으로 사용할 수는 없습니다.

*rest_of_numbers = random_numbers_descending
SyntaxError: starred assignment target must be in a list or tuple

2. 한 단계 언패킹에서는 스타 표현식을 여러 개 쓸 수 없다

동일한 단계(single-level)의 언패킹 패턴 안에는 catch-all 표현식을 두 개 이상 사용할 수 없습니다. 이 점도 꼭 기억해야 합니다.

*rest_of_numbers, *more_smallest, smallest = random_numbers_descending
SyntaxError: two starred expressions in assignment

3. 다단계(multi-level) 구조에서는 여러 개 사용 가능하다

단, 서로 다른 단계의 구조를 언패킹할 때 각각의 catch-all 역할이라면, 여러 개의 스타 표현식을 사용하는 것이 가능합니다.

player_grandslame_and_atptitles = {
    'Federer': (20, 103),
    'Nadal': (20, 84),
}

((player1, (grandslam1, *atptitles1)), (player2, (grandslam2, *atptitles2))) = player_grandslame_and_atptitles.items()

print(f'Output \nPlayer - {player1} Have acheived {grandslam1} grandslams and , {atptitles1} atp tour titles')
print(f'Player - {player2} Have acheived {grandslam2} grandslams and , {atptitles2} atp tour titles')

실행 결과

Player - Federer Have acheived 20 grandslams and , [103] atp tour titles
Player - Nadal Have acheived 20 grandslams and , [84] atp tour titles

스타 표현식은 항상 리스트가 된다

스타 표현식으로 받은 값은 어떤 경우에도 항상 리스트(list) 인스턴스가 됩니다. 만약 언패킹 과정에서 남는 요소가 없다면, catch-all 부분은 빈 리스트([])가 됩니다.

이 특징은 적어도 N개 이상의 요소가 있다는 것을 미리 알고 있는 시퀀스를 처리할 때 특히 유용합니다. 요소 개수가 부족해 예외가 날 걱정 없이 안전하게 코드를 작성할 수 있습니다.

random_numbers = [0, 1]
first, second, *rest = random_numbers
print(f"Output \n{first, second, rest}")

실행 결과

(0, 1, [])

마무리

스타 표현식을 활용한 catch-all 언패킹은 다음과 같은 상황에서 큰 힘을 발휘합니다.

- 시퀀스의 길이를 미리 알 수 없을 때
- 첫 번째(또는 마지막) 몇 개 요소와 나머지를 분리하고 싶을 때
- 인덱싱·슬라이싱으로 인한 가독성 저하와 off-by-one 오류를 피하고 싶을 때

단, 필수 요소를 하나 이상 포함해야 하고, 같은 단계에서는 스타 표현식을 하나만 쓸 수 있다는 규칙만 기억하면 됩니다. 이 두 가지만 지키면 훨씬 안전하고 읽기 좋은 파이썬 코드를 작성할 수 있습니다.