본문 바로가기
Python

[Python] asterisk * 사용하기 : unpacking, packing

by ds31x 2023. 7. 30.

C언어에서 pointer 연산자인 * (asterisk)는 Python에서 상당히 낯설게 동작한다.

(특히, PEP3132, PEP448 등에서 그 기능이 무지 많아져서... --;;)

 

double asterisk ** 와 함께 packing과 unpacking 기능으로 정말 많이 사용되기 때문에 한번은 정리를 해보는게 도움이 된다.

 

크게 function과 관련되어서는 두가지 mode로 동작한다.

  • function을 define 할 때 parameter에서 사용되는 경우 (= function header에서 사용되는 경우) : packing
  • function call에서 argument로 사용되는 경우 : unpacking

이와 유사하지만 조금 색다르게 보이는 경우도 다음과 같이 있다.

  • assignment의 left side에 놓이는 경우 : packing
  • list 를 나타내는 square brackets [ ] 안에 놓이는 경우 : unpacking
  • list 외에 set { }이나 tuple ( ) 안에 놓이는 경우 : unpacking

Function's arguments and paraemters

다음 예제는 function header에서 parameter에 사용되는 경우로 packing을 수행하는 예이다.

def test_func(*pack):
  print(type(pack),pack)
  for idx, c in enumerate(pack):
    print(f'{idx:02} : {str(c):>14}')

test_func('사과','바나나','복숭아','자두')
  • test_func에 주어진 복수의 argument들이 pack이라는 이름을 가지는 tuple 객체로 packing 됨을 확인할 수 있음.
  • positional arguments의 순서를 유지하는 immutable sequence type 인 tuple로 묶임.
  • 위와 같은 방법으로 사용되는 경우 관례적으로 *args 라고 parameter를 기재하나, 위 예제에서는 packing을 강조하기 위해 이름을 바꿈. (가급적 관례를 따르기를 권함)

결과는 다음과 같음.

<class 'tuple'> ('사과', '바나나', '복숭아', '자두')
00 :             사과
01 :            바나나
02 :            복숭아
03 :             자두

다음은 *를 function call에서의 argument로 사용할 경우의 예임.

unpack = ['사과', '바나나', '복숭아', '자두']

test_func(*unpack) # test_func('사과','바나나','복숭아','자두') 와 동일.
  • *unpack으로 argument를 넘겨주면, unpack list를 unpacking하여 각각의 item들이 arguments로 주어지게 됨.
  • 때문에 앞서 결과와 같은 arguments를 넘겨준 것과 같은 결과를 보임.

만약 다음과 같이 *를 빼고 unpack만을 argument로 넘겨주는 것을 실행해보자.

>>> test_func(unpack)
<class 'tuple'> (['사과', '바나나', '복숭아', '자두'],)
00 : ['사과', '바나나', '복숭아', '자두']
  • list unpack이 통째로 하나의 argument로 주어진다 (unpacking이 일어나지 않음)
  • 때문에 하나의 argument로 처리되고 list unpack에서의 __str__ special method의 반환값에 해당하는 문자열이 출력됨.

다른 경우들.

다음은 assignment의 left side에 놓이는 경우에 대한 예이다.

first, second, *remaining = unpack

print(first)
print(second)
print(remaining)
print(type(remaining))
  • a,b,c=1,2,3 과 같은 다중할당은 left side와 right side의 item의 수가 같아야함.
  • 하지만, 위와 같이 *remaining이 left side에서 사용되면, 나머지 item들을 모두 packing한 listr객체 remaining이 됨.
  • 이는 마치 function header에서 *로 시작하는 parameter에 여러 arguments가 packing되는 것과 개념적으로 유사함.

결과는 다음과 같음.

사과
바나나
['복숭아', '자두']
<class 'list'>

list 를 나타내는 square brackets [ ] 안에 놓는 경우에 대한 예는 다음과 같다.

>>> new_list = [ *unpack, *reversed(unpack)]
>>> new_list
['사과', '바나나', '복숭아', '자두', '자두', '복숭아', '바나나', '사과']
  • 위의 code snippet은 unpack list를 역순으로 뒤에 concatenate 시킴.

*을 이용한 unpacking을 활용한 예로서 PEP 448에서 추가된 기능이다.
이전에 위와 같은 처리는 다음과 같이 해야 했음.

new_list = [list(unpack), list(reversed(unpack))]
  • 같은 결과를 얻을 수 있지만, 좀 더 비 효율적으로 알려져 있음.

아래와 같이 timeit을 통해 확인 가능함.

import timeit
unpack = ['사과', '바나나', '복숭아', '자두']

def test_fn0 ():
  ret = [ *unpack, *reversed(unpack)]
  return ret

print('the case of * :', round(timeit.timeit(test_fn0, number= 1000000),4))

def test_fn1 ():
  ret = [list(unpack), list(reversed(unpack))]
  return ret

print('the case of old approach :', round(timeit.timeit(test_fn1, number= 1000000),4))

이를 많이 사용하는 경우는
다음과 같이 맨 앞의 item을 맨 뒤로 보내고, 나머지 item들을 하나씩 앞으로 보내는 처리 등을 할 때 매우 유용하다.

>>> new_list = [*unpack[1:],unpack[0]]
>>> new_list
['바나나', '복숭아', '자두', '사과']

또는 두 list의 union을 구할 때에도 다음과 같이 활용가능하다.

u = {*a, *b} # set().union(a,b)

Keyword-only arguments 처리하기.

function header에서 다음과 같이 *를 parameter로 사용할 경우,
특정 parameter 이후로는 positional arguments를 사용하지 못하도록 막을 수 있다.

def test_fn( a, *, key0=None):
  """ a 파라메터까지만 positional argument로 할당가능하고, 
  * 이후에 있는 key0는 keyword argument로만 사용가능함.
  """
  print(f'a={a}')
  print(f'key0={key0}')

test_fn(a)
test_fn(a,1)
  • * 이후로는 무조건 keyword argument로만 사용가능해짐.

https://gist.github.com/dsaint31x/d139f4a7dac95a301bc6a06f3bd12755

 

py_asterisk_ex.ipynb

py_asterisk_ex.ipynb. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

728x90