본문 바로가기

Python

Python - Variable Length Arguments

이번에는 파이썬에서 함수의 인자로 가변 인자를 받는 것에 대해 알아보도록 하겠습니다 :)

파이썬 관련 공부를 하면서 어떤 document를 보거나, source code를 보면 *args**kwargs를 종종 마주치게 된다. 이 둘은 함수의 파라미터의 개수가 정해지지 않았거나, 상황에 따라 매번 다른 길이의 파라미터를 넣어줘야 할 때 사용하는 가변 인자를 의미한다. 대표적으로 print function이 가변인자를 받는 함수일 것이다. 여러 개를 넣을 수 있고, 가변적이게 넣을 수 있는 것이다.)

*args

함수의 정의에서 파라미터로써 *args와 같은 표현은 positional 가변인자를 의미한다.( point는 저 위치에 *를 변수명앞에 붙인 파라미터가 의미하는 것이다.) 사실 args는 다른 변수명을 사용해도 된다. 단지 convention에 의해 args를 사용하는게 굳어져있고, 그대로 사용하는 것을 추천한다. 이렇게 함수를 정의할 때 함수의 파라미터 부분에 *args를 사용하게 되면 Positional 가변인자로 기존의 파라미터 이후에 나오는 positional argument들을 tuple로 저장하게 된다. 

def position_test(a, b, *args):
	return a+b+sum(args)
    
print(position_test(1, 2, 3, 4, 5)

# 1 + 2 + sum((3,4,5))

이렇게 함수의 정의 단계에서 가변 인자를 받을 수 있게 설정해놓게 되면, args라는 변수에는 tuple로 변수들이 들어오는 것이다. 즉 args(3, 4 ,5)인 것이다. 여기서 한 가지 주의해야 할 점은 이 * asterisk의 쓰임이다. 함수의 정의단계에서 파라미터로 사용할 경우에는 추후에 함수를 호출할 때 가변 인자로 받을 수 있게 해주는 것을 의미한다. 그 외의 위치에서 *뒤에 container를 두게 될 경우 해당 container안에 들어가 있는 값들을 unpacking해주는 역할을 하게 된다.

def position_test2(*args):
    print( args)
    print(*args)
    
position_test2(1,2,3)
>>> (1,2,3)
>>>  1,2,3

 

**kwargs

함수의 정의에서 파라미터로써 **kwargs와 같은 표현은 정확히 말하자면 해당 위치에서 변수명 앞에 **를 붙였을 때, 키워드 가변인자를 인자로 받을 수 있다는 의미다. kwargs는 마찬가지로 관습으로 keyword임을 강조하기 위해서 kwargs를 사용한다. 이렇게 되면 입력된 키워드 가변 인자들은 dict type으로 저장된다. 예시를 통해 확인해보자.

def keyword_test(**kwargs):
    print(kwargs)
    print(*kwargs)
    print(**kwargs)
    
keyword_test(a=1, b=2, c=3)
>>> {'a': 1, 'b': 2, 'c': 3}
>>> a b c
>>> TypeError: 'a' is an invalid keyword argument for print()

먼저 이렇게 키워드 가변인자를 잘 받아서 dict type으로 kwargs에 잘 저장한 것을 확인할 수 있다. 여기서 하나 더 체크할 점은 바로 *와 **의 함수 정의 시 파라미터 앞에 붙이는 것 외의 위치에서의 사용이다. 앞서 언급했듯 *는 positional unpacking의 용도로 사용된다. 따라서 dictionary type에 대해서 *를 붙이게 될 경우 다음와 같이 key값들만 나오는 것이다. 그렇다면 **는 무엇일까? 지금까지 한 것과 유사하게 **는 keyword unpakcing이다. 즉, 위와같은 딕셔너리가 keyword unpacking이 되면 'a': 1, 'b': 2, 'c': 3 가 되는 것이다. 그런데 이런 형태가 print문으로 출력이 될까? 이러한 keyword argument형태는 print문으로 출력되지 않는다. 그래서 위와 같은 에러가 발생하게 되는 것이다.

이를 출력하기 위해서는 아래와 같이 각 키워딩 값을 받아줄 위치를 지정해줘야한다.

def kwarg_test1(**kwargs):
    print(kwargs)
    print(*kwargs)
    print("{a}-{b}-{c}".format(**kwargs))

kwarg_test1( a=1 ,b=2 ,c=3)
>>> {'a': 1, 'b': 2, 'c': 3}
>>> a b c
>>> 1-2-3

그냥 보더라도 무척 불편해 보인다. 실제로 이 **의 경우에는 잘 사용할 일이 없을 가능성이 높다.

asterisk unpacking

앞서 간단히 언급했던 함수 정의 시 파라미터 앞에 붙이는 *, ** 외의 위치에서의 *, **의 사용은 unpacking을 의미한다는 것을 이야기했다. 이때 당연하게도 함수 정의 시 파라미터 앞에 붙이는 *, ** 외의 위치라는 말에는 함수 호출 시 인자 앞에 ***를 붙이는 경우도 포함된다. 따라서 함수의 인자로 sequence type의 container를 넣어줄 경우 *를 통해 unpack해서 넣어줄 수 있고, unsequence type의 container를 넣어줄 경우 **를 통해서 unpack해줄 수 있는 것이다.

# * -> sequence type unpacking

data = ([1, 2], [3, 4], [5, 6])
print(*data)
>>> [1, 2] [3, 4] [5, 6]

# ** -> unsequence type unpakcing

def unsequence_test(a, b, c, d,):
    print(a, b, c, d)
    
data = {"b":1 , "c":2, "d":3}
unsequence_test(1, **data)
>>> 1 2 3 4

 

이외에도 zip등의 함수를 사용할 때 자주 사용하게 된다. 예를 들어서 여러 개의 list가 담긴 어떤 container가 있고, 이 각각의 리스트의 요소의 위치별로 요소들을 묶어서 다른 zip container를 만들어주고자 할 때 사용할 수 있다. 아래를 살펴보자.

for data in zip(*([1, 2], [3, 4], [5, 6])):
    print(data)
>>> (1, 3, 5)
>>> (2, 4, 6)

 

마지막으로 이렇게 배운 것들을 다 사용하는 것을 보면서, *args **kwargs를 사용할 때의 주의점을 살펴보자. 어떤 함수에 파라미터로 invariable positional argument, invariable keyword argument, variable positional argument, invariable keyword argument를 모두 사용하고자 한다면 어떻게 될까? 이 때는 지금 적은 것과 같은 순서로 함수 정의 시 파라미터를 위치시켜줘야 한다. 즉, def kwargs_test_3( one, two, *args, **kwargs): 와 같이 정의해줘야 한다는 것이다. 그 이유는 이렇게 여러 가지가 섞였을 경우, 함수에서 받는 인자를 헷갈리지 않기 위해서이다. 그래서 아래와 같이 코드를 적고 실행하게 되면 invalid syntax error 즉 문법상 오류를 리턴하게 되는 것이다. 따라서 이 순서를 맞춰서 코드를 작성해주는 것이 문법상 맞는 표현이다.

# error
def kwargs_test_2 (one,two, **kwargs, *args):
    print(kwargs)

kwargs_test_3(3,4,5,6, first=3, second=4, third=5,7,8,9)
>>> def kwargs_test_3(one,two, **kwargs, *args):
>>>     SyntaxError: invalid syntax

# correct
def kwargs_test_3(one,two, *args, **kwargs):
    print(one+two+sum(args))
    print(kwargs)
    
kwargs_test_3(3,4,5,6,7,8,9, first=3, second=4, third=5)
>>> 42
>>> {'first': 3, 'second': 4, 'third': 5}

 

이렇게 파이썬에서 함수에서 가변 인자를 어떻게 다루고, container unpacking을 위해서 어떤 방법을 사용하고 있는지 살펴봤다. document를 읽을 때도 자주 마주치게 될 것이고, 다양한 github 코드들에서도 많이 사용되는 것이니 꼭 알아두자.

'Python' 카테고리의 다른 글

Python OOP - Part 1 ( Object Oriented Programming)  (0) 2022.02.11
Python - Decorator  (0) 2022.02.02
Python - Iterable / Iterator / Generator  (0) 2022.02.01
Python Scoping Rule ( by LEGB rule )  (0) 2022.02.01
Python - Call by Object Reference  (0) 2022.02.01