기본 언패킹(Better way 6)의 한 가지 한계점은 언패킹할 시퀀스의 길이를 미리 알고 있어야 한다는 것이다.
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
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-3-fd27ef4bf2c8> in <module> ----> 1 oldest, second_oldest = car_ages_descending ValueError: too many values to unpack (expected 2)
파이썬을 처음 사용하는 사람은 이런 상황에서 인덱스와 슬라이싱(Better way 11)을 자주 사용한다.
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)
20 0 [19, 15, 9, 8, 7, 6, 4, 1]
*others, second_youngest, youngest = car_ages_descending
print(youngest, second_youngest, others)
0 1 [20, 19, 15, 9, 8, 7, 6, 4]
하지만 별표 식이 포함된 언패킹 대입을 처리하려면 필수인 부분이 적어도 하나는 있어야 한다.
*others = car_ages_descending
File "<ipython-input-8-77c6f344fe32>", line 1 *others = car_ages_descending ^ SyntaxError: starred assignment target must be in a list or tuple
또한, 한 수준의 언패킹 패턴에 별표 식을 두 개 이상 쓸 수도 없다.
first, *middle, *second_middle, last = [1, 2, 3, 4]
File "<ipython-input-9-77dccc131ad1>", line 1 first, *middle, *second_middle, last = [1, 2, 3, 4] ^ SyntaxError: two starred expressions in assignment
하지만 여러 계층으로 이뤄진 구조를 언패킹할 때는 서로 다른 부분에 포함되는 한, 별표 식을 여럿 사용해도 된다.
car_inventory = {
'시내': ('그랜저', '아반테', '티코'),
'공항': ('제네시스 쿠페', '소나타', 'K5', '악센트'),
}
((loc1, (best1, *rest1)),
(loc2, (best2, *rest2))) = 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 []
언패킹 구문을 사용해 임의의 이터레이터를 가져올 수도 있지만 기본 다중 대입문보다 그다지 많이 쓸모 있지는 않다.
다음 예제는 길이가 2인 range에 들어 있는 값을 언패킹한다.
it = iter(range(1, 3))
first, second = it
print(f'{first} & {second}')
1 & 2
하지만 별표 식을 추가하면 언패킹할 이터레이터의 값을 깔끔하게 가져올 수 있다.
def generate_csv():
yield ('날짜', '제조사' , '모델', '연식', '가격')
for i in range(100):
yield ('2019-03-25', '현대', '소나타', '2010', '2400만원')
yield ('2019-03-26', '기아', '프라이드', '2008', '1400만원')
이 제너레이터를 인덱스와 슬라이스를 이용하여 처리할 수 있다.
all_csv_rows = list(generate_csv())
header = all_csv_rows[0]
rows = all_csv_rows[1:]
print('CSV 헤더:', header)
print('행 수: ', len(rows))
print(rows[0])
print(rows[1])
print(rows[2])
CSV 헤더: ('날짜', '제조사', '모델', '연식', '가격') 행 수: 200 ('2019-03-25', '현대', '소나타', '2010', '2400만원') ('2019-03-26', '기아', '프라이드', '2008', '1400만원') ('2019-03-25', '현대', '소나타', '2010', '2400만원')
별표 식으로 언패킹하면 이터레이터가 내보내는 내용 중에 첫 번째와 나머지를 쉽게 나눠서 처리할 수 있다.
it = generate_csv()
header, *rows = it
print('CSV 헤더:', header)
print('행 수: ', len(rows))
CSV 헤더: ('날짜', '제조사', '모델', '연식', '가격') 행 수: 200
print(rows[0])
print(rows[1])
print(rows[2])
('2019-03-25', '현대', '소나타', '2010', '2400만원') ('2019-03-26', '기아', '프라이드', '2008', '1400만원') ('2019-03-25', '현대', '소나타', '2010', '2400만원')
하지만 별표 식은 항상 리스트를 만들어내기 때문에 이터레이터를 별표식으로 언패킹하면 컴퓨터 메모리를 모두 다 사용해서 프로그램이 멈출 수 있다.