매일 업데이트
2022-11-19 09:18 12 min

Python에서 압축 풀기 연산자(*, **)를 사용하는 방법은 무엇입니까?

파이썬은 현재 가장 널리 사용되는 프로그래밍 언어 중 하나입니다. 오늘은 파이썬의 핵심 기능이지만 종종 간과되는 기능 중 하나를 활용하는 방법을 알아보겠습니다.

여러분이 다른 개발자의 코드에서 *** 기호를 보았거나, 실제로 그 용도를 명확히 알지 못한 채 사용해 본 경험이 있을 수 있습니다. 이번 글에서는 '언패킹'이라는 개념과 이를 활용하여 보다 효율적인 파이썬 코드를 작성하는 방법에 대해 자세히 살펴보겠습니다.

본 튜토리얼을 학습하는 동안 다음 개념들이 도움이 될 것입니다.

  • 이터러블(Iterable): for 루프를 통해 반복 처리할 수 있는 모든 시퀀스, 예를 들어 리스트, 튜플, 세트, 딕셔너리 등이 있습니다.
  • 호출 가능(Callable): 괄호 ()를 사용하여 호출할 수 있는 파이썬 객체입니다. 함수 myfunction() 등이 이에 해당합니다.
  • 셸(Shell): 파이썬 코드를 실행할 수 있는 대화형 런타임 환경입니다. 터미널에서 python 명령어를 실행하여 사용할 수 있습니다.
  • 변수(Variable): 객체를 저장하고 메모리 위치를 가리키는 기호화된 이름입니다.

가장 먼저 자주 혼동되는 부분부터 짚고 넘어가겠습니다. 파이썬에서 별표(*)는 산술 연산자로도 사용됩니다. 별표 하나(*)는 곱셈을 나타내며, 별표 두 개(**)는 지수 연산을 나타냅니다.

>>> 3*3
9
>>> 3**3
27

파이썬 셸을 실행하고 위의 코드를 직접 입력하여 확인할 수 있습니다.

참고: 이 튜토리얼을 따라 하려면 파이썬 3 버전이 설치되어 있어야 합니다. 아직 설치하지 않았다면 파이썬 설치 가이드를 참조하시기 바랍니다.

보시다시피, 숫자와 숫자 사이에 별표를 사용하면 산술 연산이 수행됩니다.

>>> *range(1, 6),
(1, 2, 3, 4, 5)
>>> {**{'vanilla':3, 'chocolate':2}, 'strawberry':2}
{'vanilla': 3, 'chocolate': 2, 'strawberry': 2}

반면, 이터러블 객체 앞에 별표(*, **)를 사용하면 해당 객체의 '언패킹'이 수행됩니다. 아래 예시를 통해 확인해 보겠습니다.

만약 지금 이 내용이 완전히 이해되지 않더라도 걱정하지 마세요. 이것은 파이썬 언패킹에 대한 간단한 서론일 뿐입니다. 튜토리얼을 계속 읽어보시면 모든 내용을 이해할 수 있을 것입니다!

언패킹이란 무엇일까요?

언패킹은 리스트, 튜플, 딕셔너리와 같은 이터러블 객체에서 요소들을 꺼내는 과정입니다. 마치 상자를 열어 케이블, 헤드폰, USB 등의 다양한 물건들을 꺼내는 것과 같다고 생각하면 됩니다.

파이썬에서 언패킹은 현실 세계에서 상자를 푸는 것과 유사한 방식으로 작동합니다.

>>> mybox = ['cables', 'headphones', 'USB']
>>> item1, item2, item3 = mybox

이해를 돕기 위해 위의 예시를 코드로 좀 더 자세히 살펴보겠습니다.

mybox 리스트 안에 있는 세 개의 항목이 각각 item1, item2, item3 변수에 할당됩니다. 이러한 형태의 변수 할당이 파이썬에서 언패킹의 기본적인 개념입니다.

>>> item1
'cables'
>>> item2
'headphones'
>>> item3
'USB'

각 항목의 값을 확인하면, item1은 "cables", item2는 "headphones" 등의 값을 가지고 있음을 알 수 있습니다.

>>> newbox = ['cables', 'headphones', 'USB', 'mouse']
>>> item1, item2, item3 = newbox
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 3)

지금까지는 문제가 없었지만, 만약 언패킹하려는 리스트에 더 많은 요소가 있고, 할당하려는 변수의 개수는 동일하게 유지한다면 어떻게 될까요?

아마도 이러한 종류의 오류가 발생할 것이라고 예상했을 것입니다. 본질적으로 4개의 리스트 항목을 3개의 변수에 할당하려는 시도를 하고 있으며, 파이썬은 어떤 값을 어디에 할당해야 할지 판단하지 못합니다. 그래서 'ValueError: too many values to unpack'이라는 오류 메시지를 반환합니다.

'언패킹할 값이 너무 많습니다'라는 메시지와 함께 값 오류가 발생합니다. 이는 왼쪽에는 3개의 변수가 설정되어 있고, 오른쪽에는 4개의 값(newbox 리스트에 해당)이 있기 때문에 발생합니다.

>>> lastbox = ['cables', 'headphones']
>>> item1, item2, item3 = lastbox
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)

비슷한 과정을 시도하지만, 언패킹할 값보다 더 많은 변수가 있는 경우, 메시지가 조금 다를 뿐 또 다른 ValueError가 발생합니다.

참고: 지금은 리스트를 사용하고 있지만, 이터러블 객체(리스트, 세트, 튜플, 딕셔너리)와 함께 이러한 형태의 언패킹을 사용할 수 있습니다.

그렇다면 이러한 상황을 어떻게 해결해야 할까요? 오류 없이 이터러블의 모든 항목을 몇 개의 변수로 언패킹할 수 있는 방법이 있을까요?

물론입니다. 바로 언패킹 연산자 또는 별표 연산자(*, **)를 사용하면 됩니다. 파이썬에서 어떻게 사용되는지 함께 알아보겠습니다.

* 연산자를 사용하여 리스트 언패킹하기

별표 연산자

>>> first, *unused, last = [1, 2, 3, 5, 7]
>>> first
1
>>> last
7
>>> unused
[2, 3, 5]

별표 연산자는 할당되지 않은 이터러블의 모든 값을 언패킹하는 데 사용됩니다.

>>> first, *_, last = [1, 2, 3, 5, 7]
>>> _
[2, 3, 5]

만약 리스트에서 첫 번째 요소와 마지막 요소만 가져오고 싶고, 인덱스를 사용하고 싶지 않다면, 별표 연산자를 사용할 수 있습니다.

>>> first, *_, last = [1, 2]
>>> first
1
>>> last
2
>>> _
[]

보시다시피 별표 연산자를 사용하면 '사용되지 않는' 모든 값을 가져올 수 있습니다. 값을 버리는 가장 일반적인 방법은 밑줄 변수(_)를 사용하는 것입니다. 이 변수는 때때로 "더미 변수"로 불립니다.

리스트에 요소가 두 개만 있는 경우에도 이 방법을 사용할 수 있습니다.

이 경우에는 밑줄 변수(더미 변수)에 빈 리스트가 저장되어, 다른 두 변수가 리스트의 사용 가능한 값에 접근할 수 있도록 합니다.

>>> *string = 'PythonIsTheBest'

일반적인 문제 해결

>>> *string = 'PythonIsTheBest'
  File "<stdin>", line 1
SyntaxError: starred assignment target must be in a list or tuple

이터러블의 고유한 요소를 풀 수 있습니다. 예를 들어 다음과 같이 생각할 수 있습니다. 그러나 위 코드는 `SyntaxError`를 반환합니다. 이는 PEP 사양에 따른 것입니다.

>>> *string, = 'PythonIsTheBest'
>>> string
['P', 'y', 't', 'h', 'o', 'n', 'I', 's', 'T', 'h', 'e', 'B', 'e', 's', 't']

간단한 할당문의 왼쪽에 있는 튜플(또는 리스트)

>>> *numbers, = range(5)
>>> numbers
[0, 1, 2, 3, 4]

이터러블의 모든 값을 단일 변수로 언패킹하려면 튜플을 설정해야 하며, 이때 쉼표 하나를 추가하는 것으로 충분합니다.

또 다른 예시로, 일련의 숫자를 반환하는 range 함수를 사용해 보겠습니다.

이제 별표를 사용하여 리스트와 튜플을 언패킹하는 방법을 알았으므로, 다음은 딕셔너리를 언패킹할 차례입니다.

** 연산자로 딕셔너리 언패킹하기

>>> **greetings, = {'hello': 'HELLO', 'bye':'BYE'} 
...
SyntaxError: invalid syntax

단일 별표는 리스트와 튜플을 언패킹하는 데 사용되지만, 이중 별표(**)는 딕셔너리를 언패킹하는 데 사용됩니다.

>>> food = {'fish':3, 'meat':5, 'pasta':9} 
>>> colors = {'red': 'intensity', 'yellow':'happiness'}
>>> merged_dict = {**food, **colors}
>>> merged_dict
{'fish': 3, 'meat': 5, 'pasta': 9, 'red': 'intensity', 'yellow': 'happiness'}

아쉽게도 튜플과 리스트에서 했던 것처럼 딕셔너리를 단일 변수로 언패킹할 수는 없습니다. 다시 말해, 아래 코드는 오류를 발생시킵니다.

그러나 ** 연산자는 호출 가능 객체 및 다른 딕셔너리 내부에서 사용할 수 있습니다. 예를 들어, 여러 딕셔너리를 결합하여 새로운 딕셔너리를 생성하려면 아래 코드를 사용할 수 있습니다.

이것은 복합 딕셔너리를 만드는 매우 간단한 방법이지만, 파이썬에서 언패킹의 주요 접근 방식은 아닙니다.

이제 호출 가능 객체와 함께 언패킹을 사용하는 방법을 살펴보겠습니다.

함수 패킹: argskwargs

클래스나 함수를 구현하기 전에 argskwargs를 본 적이 있을 것입니다. 호출 가능 객체와 함께 사용해야 하는 이유를 알아보겠습니다.

>>> def product(n1, n2):
...     return n1 * n2
... 
>>> numbers = [12, 1]
>>> product(*numbers)
12

* 연산자(args)로 패킹하기

>>> product(12, 1)
12

두 숫자의 곱을 계산하는 함수가 있다고 가정해 보겠습니다.

>>> numbers = [12, 1, 3, 4]
>>> product(*numbers)
...
TypeError: product() takes 2 positional arguments but 4 were given

보시다시피 리스트의 숫자를 함수로 언패킹하고 있기 때문에, 실제로 아래 코드를 실행하는 것과 같습니다.

>>> def product(*args):
...     result = 1
...     for i in args:
...             result *= i
...     return result
...
>>> product(*numbers)
144

여기까지는 모든 것이 잘 작동하지만, 더 긴 리스트를 전달해야 한다면 어떻게 해야 할까요? 함수가 처리할 수 있는 것보다 더 많은 인수를 받기 때문에 오류가 발생할 것입니다.

리스트를 함수에 직접 패킹함으로써 이 모든 것을 해결할 수 있습니다. 그러면 함수 내부에 이터러블이 생성되어 함수에 원하는 만큼 인수를 전달할 수 있습니다.

여기서는 args 매개변수를 이터러블로 처리하고, 요소들을 순회하면서 모든 숫자의 곱을 반환합니다. 함수의 시작 값이 1이어야 한다는 점에 주의해야 합니다. 만약 0으로 시작하면 함수는 항상 0을 반환하기 때문입니다. 참고: args는 관례일 뿐이며 다른 매개변수 이름을 사용할 수 있습니다. 또한 리스트를 사용하지 않고 내장 함수처럼 함수에 임의의 숫자를 전달할 수도 있습니다.

>>> product(5, 5, 5)
125
>>> print(5, 5, 5)
5 5 5

인쇄 기능

>>> def test_type(*args):
...     print(type(args))
...     print(args)
... 
>>> test_type(1, 2, 4, 'a string')
<class 'tuple'>
(1, 2, 4, 'a string')

.

마지막으로 함수 인수의 객체 유형을 가져옵니다.

위의 코드에서 언급한 것처럼, args의 타입은 항상 튜플이며, 그 내용은 함수에 전달된 모든 인수로 구성되어 있습니다. (키워드 인수는 제외)

** 연산자로 패킹하기 (kwargs)

>>> def make_person(name, **kwargs):
...     result = name + ': '
...     for key, value in kwargs.items():
...             result += f'{key} = {value}, '
...     return result
... 
>>> make_person('Melissa', id=12112, location='london', net_worth=12000)
'Melissa: id = 12112, location = london, net_worth = 12000, '

이전에 보았듯이 ** 연산자는 딕셔너리에서만 사용됩니다. 다시 말해서, 이 연산자를 사용하면 키-값 쌍을 매개변수로 함수에 전달할 수 있습니다.

위치 인수 'name'과 정의되지 않은 양의 키워드 인수를 받는 make_person 함수를 만들어 보겠습니다.

보시다시피 **kwargs 문은 모든 키워드 인수를 함수 내에서 반복 처리할 수 있는 딕셔너리로 변환합니다.

>>> def test_kwargs(**kwargs):
...     print(type(kwargs))
...     print(kwargs)
... 
>>> test_kwargs(random=12, parameters=21)
<class 'dict'>
{'random': 12, 'parameters': 21}

참고: kwargs는 이 매개변수의 이름을 원하는 대로 지정할 수 있는 규칙일 뿐입니다.

args와 같은 방식으로 kwargs의 유형을 확인할 수 있습니다.

>>> def my_final_function(*args, **kwargs):
...     print('Type args: ', type(args))
...     print('args: ', args)
...     print('Type kwargs: ', type(kwargs))
...     print('kwargs: ', kwargs)
... 
>>> my_final_function('Python', 'The', 'Best', language="Python", users="A lot")
Type args:  <class 'tuple'>
args:  ('Python', 'The', 'Best')
Type kwargs:  <class 'dict'>
kwargs:  {'language': 'Python', 'users': 'A lot'}

kwargs 내부 변수는 항상 함수에 전달된 키-값 쌍을 저장하는 딕셔너리가 됩니다.

마지막으로 동일한 함수에서 argskwargs를 모두 사용해 보겠습니다.

결론

  • 언패킹 연산자는 일상적인 작업에서 매우 유용합니다. 이제 개별 명령문과 함수 매개변수 모두에서 언패킹 연산자를 사용하는 방법을 알게 되었습니다.
  • 이 튜토리얼에서는 다음 내용을 배웠습니다.
  • 튜플과 리스트에는 *를 사용하고, 딕셔너리에는 **를 사용합니다.
  • 함수 및 클래스 생성자에서 언패킹 연산자를 사용할 수 있습니다.

args는 키워드가 아닌 매개변수를 함수에 전달하는 데 사용되며, kwargs는 키워드 매개변수를 함수에 전달하는 데 사용됩니다.

저자
Korea

기술 트렌드와 실용적인 팁을 전하는 लेखक입니다.