예전에 파이썬 배열 회전을 설명하면서 대강 짚고 넘어간 것이 하나 있었습니다. packing과 unpacking이였습니다. 이것을 간단하게 소개만 하고 넘어가겠습니다.

 


 먼저, 매개 변수 앞에 *를 붙이면, packing이 일어납니다. 예를 들어서, 6번째 줄에 인자 [1, 2], [3, 4], [5, 6]을 보냈습니다. 그러면, 함수가 호출이 되고 매개변수로 넘어올 때, [1, 2], [3, 4], [5, 6]이 tuple로 묶어집니다. tuple은 iterable 하니까, for loop로 순회 가능합니다. 따라서, foo([1, 2], [3, 4], [5, 6])을 호출하면 [1, 2], [3, 4], [5, 6] 순으로 출력됩니다. 

 

 

 당연하게도 foo([1, 2, 3], [4, 5, 6])을 호출하면 [1, 2, 3], [4, 5, 6]이 tuple로 packing 되고, 튜플은 순회 가능하므로, for 루프로 튜플을 순회하면 [1, 2, 3], [4, 5, 6] 순서로 출력이 됩니다.

 

 

 반대로 인수에 *을 붙여버리는 경우에는 unpacking이 일어나요. 예를 들자면, [[1, 2], [3, 4]]를 언패킹 하면 어떻게 될까요? [1, 2], [3, 4]가 리스트 안에 있는 형국이므로, 풀어버리면 [1, 2], [3, 4]가 parameter로 넘어가 버리는 효과가 나타나게 되어요. 위 예제에서는 각각 2 by 2, 2 by 3짜리 list가 foo로 unpacking 되어서 넘어갔기 때문에, 2개의 매개 변수를 받아서 처리합니다.

 

 

 실행 결과는 위와 같습니다.

 

 


 이제 이 글에서 배열 회전을 위한 1줄짜리 코드를 천천히 들여다 봅시다. zip 함수를 먼저 이용했는데요. zip 함수를 봅시다.

 

 이건 그냥 병렬 이터레이션을 돌린 거에요. 되게 어려울 까봐 예제까지 친절하게 설명되어 있는데요. 일단, zip object를 리턴합니다. 그리고 이 zip object는 yielding tuples라고 되어 있는데요. 이를 토대로, 추측을 해 보면, next와 같은 것이 호출되면 몇 번째 원소까지 리턴했다는 컨텍스트를 저장해 두고 그 이후부터 zip을 수행할 거라는 추측을 할 수 있어요. 맨 처음에 병렬로 이터를 돌리면 1번째 리스트는 'a', 2번째와 3번째 range는 0이 나옵니다. 다음에 이터를 또 돌리면 1번째 리스트는 'b', 2번째와 3번째 range는 1이 나옵니다. 또 이터를 돌리면 1번째 리스트는 'c', 2번째와 3번째 range는 2가 나와요.

 

 

 다음에 또 이터를 돌리려니 range(3)이 모두 소비되었으니 계속 next로 돌려버리면 에러인 StopIteration을 발생시킵니다. 어찌 되었던 이것은 iterable 하니, for loop로 순회할 수 있습니다.

 

 

 이제 요래 작성해 봅시다. 그러면 ret이 iterable하고, zip 함수는 for loop가 돌 때 마다, i번째 열에 있는 원소들끼리 묶어서 결과를 뱉게 되므로, (1, 4), (2, 5), (3, 6) 순으로 출력되게 됩니다.

 

 

 이제, 출력된 결과들을 뒤집으면 어떨까요? 그러면 배열이 90도 우측으로 회전한 것이 됩니다. 이것은 t[::-1]과 같이 작성하시면 크게 어렵지 않습니다. 제가 말한 것들을 종합하면 배열 회전을 단 1줄에 구현하실 수 있어요.

 

 

  2차원 list는 list 안에 list가 있는 것입니다. zip(*li)를 하면, zip에 [1, 2, 3], [4, 5, 6]이 넘어가게 됩니다. zip([1, 2, 3], [4, 5, 6])의 결과는 next가 한 번 호출되면 (1, 4)가, 두 번째로 호출되면 (2, 5), 3번째로 호출되면 (3, 6)이 리턴되는 객체입니다. 우리는 이것을 [4, 1], [5, 2], [6, 3] 순으로 list를 초기화 시켜야 하므로 [list(k[::-1]) for k in ~ ] 으로 list comprehension 문을 작성해 주었습니다.

 

 

 실행 결과는 위와 같습니다.