함수의 파라미터 args & kwargs

파이썬 코드를 구경하다보면 함수의 입력변수로 다음과 같은 것들을 참 많이 보게 된다.

  • *args
  • **kwargs

예를 들면 다음과 같은 함수가 있을 수 있다. (여기서 사용된 함수명인 func는 임의의 함수명이다.)

def func(*args):
    # func body

또는 다음과 같은 함수

def func(**kwargs):
    # func body

심지어 두개가 함께 쓰인 함수도 보인다.

def func(*args, **kwargs):
    # func body

도대체 이러한 함수들에서 사용되는 *args**kwargs는 어떤 의미를 가지고 있길래 이렇게 자주 사용되는 것일까?

하나씩 알아보도록 하자.

*args

우선 *args이다.

*args에 사용된 args라는 이름은 관례적인 표기명(convention)이다. 꼭 "args"라는 이름을 사용할 필요는 없지만 이왕이면 이런 이름을 사용하기를 바란다는 뜻이다. 이것은 args 뿐만 아니라 kwargs에도 해당된다. 잠시 그 약어의 의미에 대해서 풀어써 보면 다음과 같은 의미가 될 것이다.

  • args - arguments의 약어
  • kwargs - keyword arguments의 약어

이제 *args의 사용법에 대해서 다음 예제를 통해 알아보자.

>>> def func(*args):
...     print(args)
... 

우선 위와 같은 간단한 함수를 먼저 작성해 보자. 그리고 이 함수를 다음과 같이 사용해 보자.

>>> func(1, 2, 3)
(1, 2, 3)
>>> func(1, 2)
(1, 2)
>>> func('a', 'b', 'c', 'd')
('a', 'b', 'c', 'd')

입력이 1, 2, 3인 경우 args에는 (1, 2, 3)이라는 튜플이 대입되었고 입력이 1, 2인 경우에는 args변수에 (1, 2)라는 튜플이 대입된다. 이 사실로 유추해보면 입력인수로 *args를 사용하면 입력 인수의 갯수에 상관없이 입력되는 모든 항목이 args라는 튜플로 저장된다는 것을 알 수 있다.

즉, *args는 모든 입력인수를 튜플로 변환해 주는 변수라고 할 수 있겠다.

**kwargs

이번에는 **kwargs에 대해서 알아보자. **kwargs*args와는 달리 별표시(*)가 두 개 사용된다. 역시 이것도 예제로 알아보도록 하자.

>>> def func(**kwargs):
...     print(kwargs)
...

그리고 이 함수를 다음과 같이 사용해 보자.

>>> func(a=1)
{'a': 1}
>>> func(name='foo', age=3)
{'age': 3, 'name': 'foo'}

func 함수의 인수로 key=value 형태를 주었을 때 입력 값 전체가 kwargs라는 딕셔너리에 저장된다는 것을 알 수 있다.

즉, **kwargs는 모든 key=value 형태의 입력인수를 딕셔너리로 변환해 주는 변수라고 할 수 있겠다.

이번에는 다음과 같은 형태의 호출에 대해서 생각해 보자.

>>> func(1, 2, 3, name='foo', age=3)

위 예처럼 이렇게 입력인수의 형태가 다양한 경우 입력인수의 갯수에 상관없이 입력을 받고 싶다면 어떻게 해야 할까?

이런경우 다음의 함수를 사용하면 입력인수를 모두 처리할 수 있게 된다.

>>> def func(*args, **kwargs):
...     print(args)
...     print(kwargs)
... 
>>> func(1, 2, 3, name='foo', age=3)
(1, 2, 3)
{'age': 3, 'name': 'foo'}

1, 2, 3 과 같은 일반적인 입력은 args튜플에 저장되고 name='foo' 와 같은 형태의 입력은 kwargs 딕셔너리에 저장되는 것을 확인할 수 있다.

만약 다음과 같이 호출한다면 어떻게 될까?

>>> func(1, 2, 3, name='foo', age=3, 4, 5)

일반적인 입력인수가 키워드형태의 입력 뒤에 존재하는 경우이다. 호출하면 다음과 같은 오류를 만나게 된다.

>>> func(1, 2, 3, name='foo', age=3, 4, 5)
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg

키워드 형태의 인수뒤에 키워드 형태가 아닌 인수는 올 수 없음을 알 수 있다. 따라서 다음과 같은 형태의 호출로 변경해야 할 것이다.

>>> func(1, 2, 3, 4, 5, name='foo', age=3)
(1, 2, 3, 4, 5)
{'age': 3, 'name': 'foo'}

클래스 입력 인수

이렇게 튜플이나 딕셔너리 형태의 입력인수를 취하는 함수가 가장 많이 사용되는 경우는 클래스를 상속하여 기능을 추가할 경우이다.

다음의 예제를 보자.

class Parent:
    def __init__(self, value1, value2):
        print(value1, value2)


class Child(Parent):
    def __init__(self, *args, **kwargs):
        Parent.__init__(self, *args, **kwargs)
        # 뭔가 다른일을 수행한다.
        print("hello")


if __name__ == '__main__':
    child = Child(1,2)

Child클래스는 Parent클래스를 상속하였다. 그리고 Child 클래스는 생성자(__init__)에서 Parent의 생성자를 그대로 수행한 후에 또 다른 기능을 추가 하였다.

이때 Child 클래스의 생성자는 Parent 클래스의 생성자를 만드는 규칙에 상관없이 항상 동작시키게 만들기 위해서 *args, **kwargs 입력인수를 사용했다. 입력으로 받은 *args, **kwargs 값을 그대로 Parent클래스의 생성자에 전달하면 입력 인수의 갯수와 종류에 상관없이 항상 동작하기 때문이다. 이럴 경우 Parent클래스의 생성자가 변경되더라도 Child클래스는 수정할 필요가 없게 되는 장점이 있다.

위 예제를 실행하면 다음과 같은 결과가 출력될 것이다.

1 2
hello

박응용 1259

2020년 7월 8일 9:15 오후

목록으로