슬라이싱 대신 포괄적 언패킹 사용하기

Tasker_Jang·2026년 8월 13일
post-thumbnail

🤔 이게 왜 문제인가

기본 언패킹(unpacking)에는 분명한 한계가 하나 있습니다. 왼쪽에 적은 변수 개수와 오른쪽 시퀀스의 길이가 정확히 맞아떨어져야 한다는 점입니다. 그래서 "가장 큰 값 둘만 따로 보고 나머지는 한 덩어리로 두겠다" 같은 요구가 들어오면 대부분 인덱스와 슬라이스로 손이 갑니다.

이 방식이 만드는 코드는 겉보기에는 멀쩡하지만 유지보수 관점에서는 지뢰밭입니다. [0], [1], [2:]처럼 서로 이어져야 할 숫자가 여러 줄에 흩어져 있으니, 나중에 항목을 하나 추가할 때 한 줄만 고치고 다른 줄을 놓치는 실수가 나옵니다. 게다가 이런 오류는 예외를 던지지 않고 그냥 잘못된 값을 반환합니다. 조용히 틀리는 코드가 가장 잡기 어려운 버그입니다.

💣 흔한 실수

car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]
car_ages_descending = sorted(car_ages, reverse=True)

# 인덱스 경계값이 세 줄에 흩어져 있습니다
oldest = car_ages_descending[0]
second_oldest = car_ages_descending[1]
others = car_ages_descending[2:]  # 앞에 변수를 하나 더 넣으면 여기도 3으로 고쳐야 합니다

print(oldest, second_oldest, others)

세 줄 모두가 "앞에서 두 개"라는 하나의 규칙을 각자 다른 방식으로 표현하고 있습니다. 규칙은 하나인데 고쳐야 할 곳이 셋이라면, 그 코드는 언젠가 어긋납니다.

✅ 파이썬다운 방법

car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]
car_ages_descending = sorted(car_ages, reverse=True)

# 별표가 붙은 식이 남는 원소를 전부 받아 갑니다
oldest, second_oldest, *others = car_ages_descending
print(oldest, second_oldest, others)  # 20 19 [15, 9, 8, 7, 6, 4, 1, 0]

# 위치는 자유롭습니다. 맨 앞이나 가운데에도 놓을 수 있습니다
*others, second_youngest, youngest = car_ages_descending

# 남는 원소가 없으면 빈 리스트가 됩니다. None이 아닙니다
first, second, *rest = [1, 2]
print(rest)  # []

한 줄이 규칙 전체를 담고 있으니 항목을 추가해도 고칠 곳이 그 줄 하나뿐입니다. 인덱스 경계값이 어긋날 여지가 구조적으로 사라진다는 점에서 가독성과 안전성을 동시에 얻는 방식입니다.

이터레이터(iterator)에도 그대로 쓸 수 있어서, 헤더와 본문을 분리하는 코드가 아주 짧아집니다.

def generate_csv():
    yield ('날짜', '제조사', '모델')
    yield ('2024-01-02', '현대', '아반떼')
    yield ('2024-01-05', '기아', 'K5')


header, *rows = generate_csv()  # 이터레이터도 한 줄로 분해됩니다

print(header)      # ('날짜', '제조사', '모델')
print(len(rows))   # 2

다만 별표가 붙은 식은 언제나 리스트를 만들어 내므로, 남는 부분 전체가 메모리에 올라갑니다. 결과 크기를 가늠할 수 있을 때만 사용해야 합니다.

📎 기억할 것

  • 별표가 붙은 식을 쓰면 길이를 모르는 나머지 부분을 한 변수에 담을 수 있습니다.
  • 인덱스와 슬라이스가 여러 줄에 흩어지지 않으므로 경계값 오류가 사라집니다.
  • 별표가 붙은 식만으로는 언패킹할 수 없고, 한 수준에 두 개 이상 쓸 수도 없습니다.
  • 별표가 붙은 부분은 항상 리스트이며, 남는 원소가 없으면 빈 리스트가 됩니다.
  • 크기를 모르는 이터레이터에 사용하면 메모리를 전부 소모할 수 있으므로 주의해야 합니다.

🛠 실무에서 어디 쓸까

로그 한 줄을 파싱할 때 특히 잘 맞습니다. timestamp, level, *message_parts = line.split()처럼 쓰면 메시지에 공백이 몇 개 들어오든 앞의 두 필드는 안전하게 뽑히고, 나머지는 ' '.join(message_parts)로 복원하면 됩니다.

랭킹 API 응답을 가공할 때도 유용합니다. 상위 세 명만 강조하고 나머지는 목록으로 내려보내는 화면이라면 first, second, third, *rest = ranking 한 줄로 끝납니다. 다만 페이지네이션 없이 수십만 건이 내려오는 엔드포인트라면 itertools.islice로 먼저 잘라 내는 편이 안전합니다.

profile
ML Engineer 🧠 | AI 모델 개발과 최적화 경험을 기록하며 성장하는 개발자 🚀 The light that burns twice as bright burns half as long ✨

0개의 댓글