파이썬코딩의기술 - BW13 슬라이싱보다는 나머지를 모두 잡아내는 언패킹을 사용하라
기본 언패킹의 한 가지 한계점은 언패킹할 시퀀스의 길이를 미리 알고 있어야 한다는 것입니다.
기본 언패킹으로 리스트 맨 앞에서 원소를 두 개 가져오면 실행 시점에 예외가 발생합니다.
- (예) 중고차 매매상에서 판매하는 자동차들이 출고 이후 몇 년이 지났는지를 표현하는 리스트가 있다고 할때
car_ages = [0, 9, 4, 8, 7, 20, 19, 1, 6, 15]
car_ages_descending = sorted(car_ages, reverse=True)
oldest, second_oldest = car_ages_descending
>>
Traceback...
ValueError: too many values to unpack (expected 2)
파이썬 입문자는 이런 상황에서 인덱스와 슬라이싱을 자주 사용합니다.
- (예) 원소가 최소 두 개 이상 들어있는 리스트에서 가장 오래된 자동차와 두번째로 오래된 자동차의 나이를 가져오는 코드
oldest = car_ages_descending[0]
second_oldest = car_ages_descending[1]
others = car_ages_descending[2:]
print(oldest, second_oldest, others)
>>>
20 19 [15, 9, 8, 7, 6, 4, 1, 0]
실제로 이런 식으로 시퀀스의 원소를 여러 하위 집합으로 나누면 1 차이 나는 인덱스로 인한 오류(off-by-one-error)를 만들어내기 쉽습니다.
- (예) 어느 한 줄에서 범위를 변경했는데 다른 줄을 깜빡하고 고치지 않으면 결과가 잘못되거나 예외가 발생할 수 있습니다.
이런 상황을 더 잘 다룰 수 있도록 파이썬은 별표 식(starred expression)을 사용해 모든 값을 담는 언패킹을 할 수 있게 지원합니다.
이 구문을 사용하면 언패킹 패턴의 다른 부분에 들어가지 못하는 모든 값을 별이 붙은 부분에 다 담을 수 있습니다.
oldest, second_oldest, *others = car_ages_descending
print(oldest, second_oldest, others)
>>>
20 19 [15, 9, 8, 7, 6, 4, 1, 0]
별표 식을 다른 위치에 쓸 수도 있습니다.
여분의 슬라이스가 하나 필요한 경우, 나머지를 모두 잡아내는 이 기능의 이점을 살릴 수 있습니다.
oldest, *others, youngest = car_ages_descending
print(oldest, youngest, others)
*others, second_youngest, youngest = car_ages_descending
print(youngest, second_youngest, others)
>>>
20 0 [19, 15, 9, 8, 7, 6, 4, 1]
0 1 [20, 19, 15, 9, 8, 7, 6, 4]
(주의!!) 별표 식이 포함된 언패킹 대입을 처리하려면 필수인 부분이 적어도 하나는 있어야 합니다.
그렇지 않으면 SyntaxError가 발생합니다. 별표 식만 사용해 언패킹할 수는 없습니다.
*others = car_ages_descending
>>>
Traceback ...
SyntaxError : starred assignment target must be in a list or tuple
또한, 한 수준의 언패킹 패턴에 별표 식을 두 개 이상 쓸수도 없습니다.
first, *middle, *second_middle, last = [1, 2, 3, 4]
>>>
Traceback ...
SyntaxError : two starred expressions in assignment
여러 계층으로 이뤄진 구조를 언패킹할 때는 서로 다른 부분에 포함되는 한, 별표 식을 여럿 사용해도 됩니다.
car_inventory = {
'시내' : ('그랜저', '아반떼', '티코'),
'공항' : ('제네시스 쿠페', '소나타', 'K5', '엑센트'),
}
((loc1, (best1, *rest1)),
(loc2, (best2, *reset2))) = car_inventory.items()
print(f'{loc1} 최고는 {best1}, 나머지는 {len(rest1)} 종')
print(f'{loc2} 최고는 {best2}, 나머지는 {len(rest2)} 종')
>>>
시내 최고는 그랜저, 나머지는 2종
공항 최고는 제네시스 쿠페, 나머지는 3종
별표 식은 항상 list 인스턴스가 됩니다. 언패킹하는 시퀀스에 남는 우너소가 없으면 별표 식 부분은 빈 리스트가 됩니다.
short_list = [1, 2]
first, second, *rest = short_list
print(first, second, rest)
>>>
1 2 []
별표 식을 추가하면 언패킹할 이터레이터의 값을 깔끔하게 가져올 수 있습니다.
(예) 이번 주에 중고차 매매상에서 판매한 자동차 내역이 들어있는 CSV 파일의 각 줄을 내보내는 제너레이터가 있다고 할때 ..
이 제너레이터의 결과를 인덱스와 슬라이스를 사용해 처리해도 좋지만, 처리하는데 여러 줄이 필요하고 시각적으로 보기 않좋습니다.
def generate_csv():
yield('날짜', '제조사', '모델', '연식', '가격')
...
all_csv_rows = list(generate_csv())
header = all_csv_rows[0]
rows = all_csv_rows[1:]
print('CSV 헤더 :', header)
print('행 수 :', len(rows))
>>>
CSV 헤더 : ('날짜', '제조사', '모델', '연식', '가격')
행수 : 200
별표 식으로 언패킹하면 이터레이터가 내보내는 내용 중 첫 번째(헤더)와 나머지를 쉽게 나눠서 처리할 수 있습니다.
it = generate_csv()
header, *rows = it
print('CSV 헤더 : ', header)
print('행 수 :', len(rows))
>>>
CSV 헤더 : ('날짜', '제조사', '모델', '연식', '가격')
행 수 : 200
(주의!!) 별 표식은 항상 리스트를 만들어내기 때문에 이터레이터를 별표 식으로 언패킹하면 컴퓨터 메모리를 모두 다 사용해서 프로그램이 멈출 수 있습니다.
따.라.서 !!
결과 데이터가 모두 메모리에 들어갈 수 있다고 확신할 때만 나머지를 모두 잡아내는 언패킹을 사용해야합니다.
※ 기억해야 할 내용
▪ 언패킹 대입에 별표 식을 사용하면 언패킹 패턴에서 대입되지 않는 모든 부분을 리스트에 잡아낼 수 있습니다.
▪ 별표 식은 언패킹 패턴의 어떤 위치에든 놓을 수 있고, 별표 식에 대입된 결과는 항상 리스트가 되며, 이 리스트에는 별표 식이 받은 값이 0개 또는 그 이상 들어갑니다.
▪ 리스트를 서로 겹치지 않게 여러 조각으로 나눌 경우, 슬라이싱과 인덱싱을 사용하기보다는 나머지를 모두 잡아내는 언패킹을 사용해야 실수할 여지가 훨씬 줄어듭니다.