4 minute read

파이썬의 인자

이번 포스팅에서는 파이썬에서 함수에 전달된 인자가 어떻게 처리될 지에 대해서 다뤄보도록 하겠습니다.
다룰 내용은 아래와 같습니다.

  1. 변형 가능한 타입과 변형이 불가능한 타입이 각각 인자로 주어졌을 때의 차이
  2. 가변인자
    • 위치 가변인자
    • 키워드 가변인자

파이썬 인자의 복사

파이썬에서 함수에 전달된 인자는 값에 의해 전달됩니다.

즉 3이라는 숫자를 값으로 가진 변수를 인자로 전달하면 3 값이 전달이 됩니다.
따라서 변수 자체가 전달된 것이 아니라 값이 전달된 것이므로 전달된 값을 바꿔도 기존 변수인 var의 값은 바뀌지 않습니다.

def func(var):
    var += 3

var = 3
func(var)   # 3이라는 값이 전달됨
print(var)  # 그대로 3 출력

그런데 만약 리스트형의 변수를 함수에 전달하면 어떻게 될까요?
리스트형 변수의 경우 변수는 리스트 내용을 담고 있는 것이 아니라 리스트 내용에 대한 참조값을 가지고 있습니다.

따라서 함수 내에서 전달된 값을 이용해 리스트를 업데이트하면 참조값을 통해 외부 변수와 동일한 리스트에 접근해 값을 업데이트하게 되므로 기존 변수를 출력했을 때 나오는 결과도 바뀌게 됩니다.

def func(var):
    var.append(4)

var = [1, 2, 3]
func(var)   # [1, 2, 3] 리스트의 참조값 전달됨
print(var)  # [1, 2, 3, 4] 로 변경됨

이와 같은 차이 때문에 파이썬에서는 전달된 인자의 자료형에 따라 기존 변수의 내용이 변경이 되는 경우도 있고 변경되지 않는 경우도 있습니다.
대표적으로 int, float, string 등이 인자로 넘어갔을 때는 값이 변경되지 않고 list, dict, object 등은 값이 변경됩니다.

가변인자

가변 인자를 사용하기 위해서는 인자 앞에 *를 붙여서 사용합니다.
파이썬에서는 여러 개의 인자를 개수를 정해놓지 않고 받고 싶을 때 *args 형태로 인자를 받습니다.
또한 파라미터의 이름을 키로 사용하고 파라미터의 값을 키에 매핑되는 값으로 사용하고 싶을 때는 **kwargs 형태로 인자를 받습니다.

*args 형태의 인자를 위치 가변인자라고 하고, **kwargs 형태의 인자를 키워드 가변인자라고 합니다.

위치 가변인자

파라미터 개수가 가변적인 인자를 위치 가변 인자 라고 합니다.
위치 가변인자에 대해 알아보기 전에 함수의 패킹/언패킹에 대해 알아두면 더 이해하기 쉬우므로, 패킹/언패킹에 대해 알아보도록 하겠습니다.

패킹/언패킹

리스트 내에 있는 값 하나하나를 변수에 할당하고 싶을 때 다음과 같이 코드를 작성할 수 있습니다.

one, two, three, four, five = [1, 2, 3, 4, 5]

이처럼 코드를 쓰면 리스트 내의 값이 각 변수에 할당됩니다.
이렇게 변수에 각 값들을 할당하는 것을 언패킹이라고 합니다.

언패킹은 부분적으로도 진행할 수 있습니다.

one, *rest = [1, 2, 3, 4, 5]
print(one)          # 1
print(rest)         # [2, 3, 4, 5]
print(type(rest))   # list

이처럼 one 변수에 1만 언패킹하였고, 다른 원소들은 언패킹하지 않았습니다.

여기서 알아두면 좋을 것이 나머지 원소를 *rest 형태로 받았다는 점입니다.
변수 앞에 *를 붙이는 것은 패킹 기법을 사용한다는 뜻입니다.
위에서 rest는 결과로 나오는 2, 3, 4, 5 값을 패킹하여 넣을 변수이므로 앞에 *를 붙여줍니다.

이해하기 쉽도록 설명하면 *리스트형변수 형태로 앞에 *을 붙이면 리스트 내부의 원소들이 낱개로 나온다고 생각하시면 됩니다.
앞에서 언패킹하며 나며지 원소들에 대해 *rest를 쓴 이유도 나머지 여러개의 원소들이 낱개로 나오기 때문입니다.
각각 나온 여러개의 원소들을 모두 포함할 수 있도록 *rest라고 써줘 결과적으로는 rest라는 리스트형 변수 내의 유동적인 개수의 원소들을 모두 저장한 것입니다.

위치 가변인자 예시

패킹/언패킹에 대해 알게 되었으니 인자에서 이를 이용해 위치 가변인자를 어떻게 받는지 알아보도록 하겠습니다.
가변 인자를 받기 위해서는 함수에서 인자를 *인자 형태로 받아야합니다.

def func(*args):
    ...

위와 같은 형태로 인자를 받으면 함수를 호출할 때 아래와 같이 코드를 작성하게 됩니다.

func(1, 2, 3, 4, 5)

앞서서 *가 리스트형 변수 앞에 있으면 내부 원소들이 낱개로 나와있는 상황과 매칭된다고 하였습니다.
따라서 *args는 인자로 전해진 1, 2, 3, 4, 5 과 매칭됩니다.
또한 별표를 뗀 args[1, 2, 3, 4, 5] 리스트와 매칭됩니다.

다음 예시를 함께 보도록 하겠습니다.

# 가변 인자를 사용한 함수
def variadic_func(*elements):
    for element in elements:
        print(element)

if __name__ == "__main__":
    lst = [1, 2, 3, 4, 5]
    one, two, three, *last = lst

    variadic_func(one, two, three) # 1 2 3
    variadic_func(last)     # [4, 5]
    variadic_func(*last)    # 4 5

위의 variadic_func은 가변 인자를 인자로 받고 받은 인자를 하나하나 출력합니다.
아래 메인 코드에서는 [1, 2, 3, 4, 5] 값을 가진 리스트를 1, 2, 3 원소는 언패킹하고 4, 5 원소는 패킹하여 last 변수에 넣어두었습니다.

variadic_func은 가변인자를 받기 때문에 여러개의 값을 그냥 나열하여 인자로 전달할 수 있습니다.
따라서 variadic_func(one, two, three) 로 함수를 호출하면 함수 내부에서 elements = [1, 2, 3] 가 됩니다.

그런데 variadic_func(last) 형태로 함수를 호출하면 어떻게 될까요? last 변수는 [4, 5] 값을 가진 리스트형의 변수입니다.
따라서 variadic_func 함수 내부에서 elements = [[4, 5]] 가 됩니다.
즉, variadic_func에서 4, 5 각각을 원소로 받는 것이 아니라 [4, 5] 라는 리스트형의 원소를 인자로 받은 셈이 된 것입니다.

마지막으로 variadic_func(*last) 형태로 함수를 호출한 경우도 살펴보도록 하겠습니다. 리스트형인 last에 *을 붙여 언패킹한 형태로 함수에 전달했기 때문에 4,5 원소가 낱개로 함수에 전달됩니다.
따라서 여기에선 elements = [4, 5]가 되어 4, 5가 각각 출력되는 것입니다.

키워드 가변인자

키워드 가변인자는 파라미터의 이름으로 키를 사용하고, 파라미터의 값으로 키에 대칭되는 값을 세팅합니다.

앞에서 *args 형태로 인자를 받는 경우 1, 2, 3 이 전달됐다면

  • *args1, 2, 3 낱개의 상태이고,
  • args[1, 2, 3]값을 갖는 리스트형의 변수라고 하였습니다.

이와 비슷하게 **kwargs 형태로 인자를 받는 경우 one=1, two=2 가 전달됐다면

  • **kwargsone=1, two=2의 상태이고,
  • kwargs{'one': 1, 'two:2} 값을 갖는 사전형의 변수입니다.

따라서 func(**{"one": 1})func(one=1) 과 동일합니다.

아래 코드를 함께 살펴보도록 하겠습니다.

def variadic_func(**elements):
    print(elements)

if __name__ == "__main__":
    dictionary = {'a': 'A', 'b': 'B', 'c': 'C'}
    
    variadic_func(**dictionary)         # {'a': 'A', 'b': 'B', 'c': 'C'}
    variadic_func(dictionary=dictionary)    # {'dictionary': {'a': 'A', 'b': 'B', 'c': 'C'}}
    variadic_func(key1="value1", key2="value2", key3="value3")  # {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}

위 코드에서 variadic_func 함수는 **elements를 인자로 받습니다.
따라서 키워드를 제공해서 variadic_func에 인자를 제공하면, 각각의 인자들이 elements라는 사전으로 패킹이 됩니다.
그러므로 아래 각 함수의 호출마다 다음과 같은 상황으로 해석이 됩니다.

  • variadic_func(**dictionary)로 호출
    • dictionary의 값을 언패킹하여 전달하였으므로 **dictionary == **elements 의 상황
    • 키워드 인자들이 패킹되어 elements는 {‘a’: ‘A’, ‘b’: ‘B’, ‘c’: ‘C’} 값을 갖는 사전
  • variadic_func(dictionary=dictionary)로 호출
    • dictionary라는 키워드에 dict형의 dictionary 변수를 할당하여 호출
    • dictionary={‘a’: ‘A’, ‘b’: ‘B’, ‘c’: ‘C’} 가 전달된 것이므로 이를 패킹하여 elements에 할당
    • elements에는 {‘dictionary’: {‘a’: ‘A’, ‘b’: ‘B’, ‘c’: ‘C’}}값이 저장됨
  • variadic_func(key1="value1", key2="value2", key3="value3") 로 호출
    • 키워드와 함께 제공된 인자들이 패킹되어 elements에 사전 형식으로 저장
    • elements에는 {‘key1’: ‘value1’, ‘key2’: ‘value2’, ‘key3’: ‘value3’} 값이 저장됨

이처럼 파이썬에서는 *를 사용하여 위치 가변 인자를, **를 사용해서 키워드 가변 인자를 넘길 수 있습니다.