상위 목록: 하위 목록: 작성 날짜: 읽는 데 10 분 소요

인자(Argument)

인자(Argument)는 매개변수(Parameter)에 전달되는 실질적인 값(value)을 의미합니다.

함수에 정의된 값을 전달하는 것이 인자가 됩니다.

def func(a, b)에서 a, b는 매개변수가 되며, a와 b에 전달하는 값이 인자입니다.

Python에서는 사전에 정의되지 않은 여러 개의 인자를 전달할 수 있습니다.


매개변수는 함수를 정의할 때 작성하는 이름이고, 인자는 함수를 호출할 때 전달하는 값입니다.

제 20강에서 다룬 사용자 정의 함수는 정해진 개수의 매개변수만 사용했지만, print()처럼 인자를 몇 개든 받는 함수나, 옵션을 이름으로 골라 전달하는 함수도 필요합니다.

이번 강좌에서는 인자를 전달하는 두 가지 방식인 위치 인자와 키워드 인자를 먼저 정리한 다음, 개수가 정해지지 않은 인자를 받는 *args와 **kwargs, 호출할 때 인자를 풀어서 전달하는 방법, 그리고 위치로만 전달받는 위치 전용 매개변수를 다룹니다.



위치 인자와 키워드 인자

인자를 전달하는 방법은 두 가지입니다.

위치 인자(Positional Argument)는 func(1, 2)처럼 순서대로 전달하는 인자이며, 키워드 인자(Keyword Argument)는 func(a=1, b=2)처럼 매개변수의 이름을 지정해 전달하는 인자입니다.


def introduce(name, age, city="서울"):
    print(name, age, city)


introduce("박XX", 20)
introduce("김XX", 30, "부산")
introduce(age=40, name="이XX")
introduce("최XX", city="대전", age=50)
결과
박XX 20 서울
김XX 30 부산
이XX 40 서울
최XX 50 대전

첫 번째 호출은 위치 인자 두 개를 전달하므로 name과 age에 차례대로 할당되고, city는 기본값(default value)인 서울이 됩니다.

두 번째 호출은 위치 인자 세 개로 city까지 지정합니다.

세 번째 호출은 키워드 인자만 사용하므로 순서를 바꿔도 이름에 맞게 할당됩니다.

네 번째 호출처럼 위치 인자와 키워드 인자를 함께 사용할 수 있으며, 이때 위치 인자가 먼저 매개변수에 할당되고 남은 매개변수에 키워드 인자가 할당됩니다.


introduce(name="박XX", 20)
결과
SyntaxError: positional argument follows keyword argument

위치 인자와 키워드 인자를 함께 사용할 때는 위치 인자를 반드시 앞에 작성해야 합니다.

키워드 인자 뒤에 위치 인자를 작성하면, 그 값이 어떤 매개변수에 들어가야 하는지 알 수 없으므로 SyntaxError가 발생합니다.

이 오류는 문법 오류이므로 함수를 실행하기 전, 코드를 읽는 단계에서 발생합니다.


def introduce(name, age, city="서울"):
    print(name, age, city)


try:
    introduce("박XX", name="김XX", age=20)
except TypeError as e:
    print("TypeError:", e)

try:
    introduce("박XX")
except TypeError as e:
    print("TypeError:", e)

try:
    introduce("박XX", 20, "서울", "한국")
except TypeError as e:
    print("TypeError:", e)

try:
    introduce("박XX", 20, country="한국")
except TypeError as e:
    print("TypeError:", e)
결과
TypeError: introduce() got multiple values for argument ‘name’
TypeError: introduce() missing 1 required positional argument: ‘age’
TypeError: introduce() takes from 2 to 3 positional arguments but 4 were given
TypeError: introduce() got an unexpected keyword argument ‘country’

인자를 잘못 전달하면 TypeError가 발생하며, 오류 메시지로 원인을 알 수 있습니다.

첫 번째는 "박XX"가 위치 인자로 이미 name에 할당되었는데, name="김XX"로 같은 매개변수에 값을 한 번 더 전달했기 때문입니다.

두 번째는 기본값이 없는 age를 전달하지 않았기 때문입니다.

세 번째는 매개변수가 최대 3개인데 위치 인자를 4개 전달했기 때문이며, 네 번째는 존재하지 않는 이름(country)으로 키워드 인자를 전달했기 때문입니다.

세 번째와 네 번째처럼 정해진 것보다 많은 인자를 받고 싶을 때 사용하는 것이 다음에 다룰 *args와 **kwargs입니다.


def add_item(item, box=[]):
    box.append(item)
    return box


print(add_item("A"))
print(add_item("B"))
결과
[‘A’]
[‘A’, ‘B’]

기본값을 사용할 때 자주 하는 실수가 있습니다.

두 번째 호출은 ['B']가 출력될 것 같지만, 실제로는 첫 번째 호출에서 추가한 A가 남아 있습니다.

기본값은 함수를 정의할 때 한 번만 만들어지고, 이후 호출에서는 같은 객체를 계속 사용하기 때문입니다.

그러므로, 기본값이 목록이나 사전처럼 변경 가능한 객체라면, 한 호출에서 수정한 내용이 다음 호출에 그대로 남습니다.


def add_item(item, box=None):
    if box is None:
        box = []
    box.append(item)
    return box


print(add_item("A"))
print(add_item("B"))
결과
[‘A’]
[‘B’]

변경 가능한 기본값이 필요하다면, 기본값을 None으로 두고 함수 안에서 호출할 때마다 새 객체를 생성합니다.

box is None일 때만 새 목록을 만들므로, 호출할 때마다 빈 목록에서 시작합니다.



*args(Tuple)

함수에 전달할 인자의 개수를 미리 정할 수 없는 경우가 있습니다.

예를 들어, 숫자를 몇 개든 받아 합계를 구하는 함수는 매개변수를 a, b, c, ...처럼 미리 정해 둘 수 없습니다.

이때 매개변수 이름 앞에 *를 붙이면, 전달된 위치 인자를 개수에 상관없이 모두 받을 수 있습니다.


def func(*args):
    print("Type:", type(args))
    print("Length:", len(args))
    print(args)


func([1, 2], 3, 4, (5))
결과
Type: <class ‘tuple’>
Length: 4
([1, 2], 3, 4, 5)

매개변수를 할당할 때 *args를 사용한다면, 여러 개의 인자를 받아 처리할 수 있습니다.

args는 argument의 약어로, 매개변수의 이름을 *args가 아닌, 다른 이름으로도 할당이 가능합니다.

매개변수의 이름 앞에 애스터리스크(Asterisk, *)를 작성하면, 남은 위치 인자들을 튜플로 묶어 처리합니다.

입력된 인자들의 순서에 따라 args 변수에 값이 할당됩니다.

결과에서 [1, 2]는 목록 그대로 하나의 인자로 들어가므로, 인자의 개수는 4개입니다. *args는 전달된 인자를 하나씩 묶을 뿐, 목록 안의 요소를 풀지는 않습니다.

  • Tip : (5)는 튜플이 아닌 정수 5입니다. 요소가 하나인 튜플은 (5,)처럼 쉼표를 붙여야 합니다.


def total(*numbers):
    result = 0
    for number in numbers:
        result += number
    return result


print(total())
print(total(1))
print(total(1, 2, 3, 4, 5))
결과
0
1
15

*numbers는 인자가 없으면 빈 튜플 ()이 되므로, total()처럼 인자 없이 호출해도 오류가 발생하지 않습니다.

numbers는 튜플이므로 for 문으로 반복하거나, len(), 색인 등 튜플의 기능을 그대로 사용할 수 있습니다.

print(), max(), min() 같은 내장 함수도 이와 같은 방식으로 여러 개의 인자를 받습니다.


def total(*numbers):
    return sum(numbers)


data = [1, 2, 3]
print(total(data))
결과
TypeError: unsupported operand type(s) for +: ‘int’ and ‘list’

자주 하는 실수는 목록을 그대로 전달하는 것입니다.

total(data)는 인자 하나([1, 2, 3])를 전달하므로, numbers는 ([1, 2, 3],)가 되어 sum이 정수 0에 목록을 더하려다 TypeError가 발생합니다.

목록의 요소를 각각의 인자로 전달하려면 total(*data)처럼 인자 풀기를 사용해야 하며, 이는 아래의 인자 풀기(Unpacking)에서 다룹니다.



**kwargs(Dictionary)

*args가 개수가 정해지지 않은 위치 인자를 받는다면, **kwargs는 개수가 정해지지 않은 키워드 인자를 받습니다.

함수에 전달할 옵션의 종류를 미리 모두 정할 수 없거나, 받은 옵션을 다른 함수에 그대로 넘겨줄 때 사용합니다.


def func(**kwargs):
    print("Type:", type(kwargs))
    print("Length:", len(kwargs))
    print(kwargs)


func(a=1, b=2, c=3)
결과
Type: <class ‘dict’>
Length: 3
{‘a’: 1, ‘b’: 2, ‘c’: 3}

매개변수를 할당할 때 **kwargs를 사용한다면, 여러 개의 인자를 받아 처리할 수 있습니다.

kwargs는 keyword argument의 약어로, 매개변수의 이름을 **kwargs가 아닌, 다른 이름으로도 할당이 가능합니다.

매개변수의 이름 앞에 애스터리스크(Asterisk, *)를 두 번 작성하면, 남은 키워드 인자들을 사전으로 묶어 처리합니다.

입력된 인자들의 키 값에 따라 kwargs 변수에 값이 할당됩니다.

키워드 인자의 이름은 문자열 키가 되고, 전달한 값은 사전의 값이 됩니다. 사전에는 인자를 전달한 순서대로 저장됩니다.


def make_profile(name, **info):
    profile = {"name": name}
    profile.update(info)
    return profile


print(make_profile("박XX"))
print(make_profile("김XX", age=30, city="부산"))
결과
{‘name’: ‘박XX’}
{‘name’: ‘김XX’, ‘age’: 30, ‘city’: ‘부산’}

**kwargs는 일반 매개변수와 함께 사용할 수 있습니다.

name은 반드시 전달해야 하는 매개변수이고, 그 외의 정보는 필요한 만큼 키워드 인자로 전달합니다.

키워드 인자가 없으면 info는 빈 사전 {}이 되므로, 첫 번째 호출처럼 name만 전달해도 됩니다.


**kwargs는 편리하지만, 어떤 이름의 인자를 받는지 함수 정의만 보고 알 수 없다는 단점이 있습니다.

또한, colr="red"처럼 이름을 잘못 입력해도 오류 없이 사전에 저장되므로, 오타를 발견하기 어렵습니다.

그러므로, 받을 인자의 이름이 정해져 있다면 **kwargs 대신 매개변수를 직접 정의하는 것이 좋습니다.



*args(Tuple), **kwargs(Dictionary) 혼용

*args와 **kwargs를 함께 사용하면, 어떤 형태의 인자든 받을 수 있는 함수를 만들 수 있습니다.


def func(*data, **method):
    num = sum(data) * method["scale"]
    print(num, method["unit"] + "입니다.")


func(3, 4, 5, scale=10, unit="개")
결과
120 개입니다.

*args와 **kwargs를 동시에 사용한다면, *args, **kwargs 순서로 사용합니다.

키(Key) 값이 할당되지 않은 인자는 *args로 처리하며, 할당된 인자는 **kwargs로 처리합니다.

예제에서 data는 (3, 4, 5), method는 {'scale': 10, 'unit': '개'}가 되므로, 12 * 10 = 120이 출력됩니다.


def func(*data, **method):
    num = sum(data) * method.get("scale", 1)
    print(num, method.get("unit", "") + "입니다.")


func(3, 4, 5)
func(3, 4, 5, scale=10, unit="개")
결과
12 입니다.
120 개입니다.

앞의 예제에서 scale이나 unit을 전달하지 않으면, method["scale"]에서 KeyError가 발생합니다.

**kwargs로 받은 값은 전달되지 않을 수도 있으므로, 사전의 get(키, 기본값) 메서드로 값이 없을 때 사용할 기본값을 지정하는 것이 안전합니다.

기본 인자가 포함되는 경우 다음과 같이 사용할 수 있습니다.


def func(*data, message, **method):
    print(message)

    num = sum(data) * method["scale"]
    print(num, method["unit"] + "입니다.")

func(3, 4, 5, message="계산된 값입니다.", scale=10, unit="개")
결과
계산된 값입니다.
120 개입니다.

*args 뒤에 선언된 message는 위치로 값을 받을 수 없는 키워드 전용 매개변수(Keyword-Only Parameter)가 됩니다.

*args가 남은 위치 인자를 모두 가져가기 때문에, *args 뒤의 매개변수에는 반드시 message="..."처럼 키(Key)를 명시해 전달해야 합니다.

message에 키(Key)를 명시하지 않으면, 그 값도 *args에 포함되어 TypeError: func() missing 1 required keyword-only argument: 'message' 오류가 발생합니다.

**kwargs는 항상 가장 마지막에 선언해야 하며, 그 뒤에는 어떤 매개변수도 올 수 없습니다.

반대로 *args보다 앞에 선언된 매개변수는 위치 인자(Positional Argument)로 전달할 수 있어 키(Key)를 명시하지 않아도 됩니다.

  • Tip : 키워드 전용 매개변수는 제 33강에서 자세히 다룹니다.

위치 인자가 앞에 포함되는 경우 다음과 같이 사용할 수 있습니다.


def func(message1, message2, *data, **method):
    print(message1)
    print(message2)

    num = sum(data) * method["scale"]
    print(num, method["unit"] + "입니다.")

func("계산된 값입니다.", "값이 10배 커집니다.", 3, 4, 5, scale=10, unit="개")
결과
계산된 값입니다.
값이 10배 커집니다.
120 개입니다.

위치 인자가 앞에 포함되면, 매개변수의 순서대로 값이 할당됩니다.

*args(Tuple)와 **kwargs(Dictionary)를 혼용해 사용하는 경우 매개변수의 할당 순서가 중요합니다.

매개변수의 개수와 일치하지 않는 경우, message2에 3이 할당되어 함수가 잘못된 결과를 반환할 수도 있습니다.

예를 들어, 메시지를 하나만 전달하고 func("계산된 값입니다.", 3, 4, 5, ...)로 호출하면, 오류 없이 message2에 3이 들어가고 data는 (4, 5)가 됩니다. 이처럼 위치 인자는 개수가 맞지 않아도 오류 없이 잘못 할당될 수 있으므로 주의합니다.


def func(a, b=2, /, c=3, *args, d, e=5, **kwargs):
    print(a, b, c, args, d, e, kwargs)


func(1, d=4)
func(1, 20, 30, 40, 50, d=4, f=6)
결과
1 2 3 () 4 5 {}
1 20 30 (40, 50) 4 5 {‘f’: 6}

모든 종류의 매개변수를 함께 사용하면, 매개변수는 다음 순서로 작성해야 합니다.

위치 전용 매개변수 → / → 일반 매개변수 → *args → 키워드 전용 매개변수 → **kwargs

첫 번째 호출은 필수 매개변수인 a와 d만 전달하므로, 나머지는 기본값이 사용되고 args와 kwargs는 비어 있습니다.

두 번째 호출은 위치 인자 1, 20, 30이 a, b, c에 할당되고, 남은 40, 50은 args에 모입니다. 키워드 인자 중 d는 매개변수 d에, 정의되지 않은 f는 kwargs에 저장됩니다.


def log_call(func, *args, **kwargs):
    print("호출:", func.__name__, args, kwargs)
    return func(*args, **kwargs)


result = log_call(round, 3.14159, ndigits=2)
print(result)

log_call(print, "A", "B", sep="-")
결과
호출: round (3.14159,) {‘ndigits’: 2}
3.14
호출: print (‘A’, ‘B’) {‘sep’: ‘-‘}
A-B

*args와 **kwargs를 함께 사용하는 대표적인 용도는 받은 인자를 다른 함수에 그대로 전달하는 것입니다.

log_call은 어떤 함수든, 어떤 인자든 받아서 호출 정보를 출력한 뒤 원래 함수를 호출합니다.

func(*args, **kwargs)는 받은 위치 인자와 키워드 인자를 원래 형태 그대로 풀어서 전달하므로, round(3.14159, ndigits=2)와 print("A", "B", sep="-")가 실행됩니다.

이 형태는 제 47강에서 다루는 데코레이터에서 자주 사용됩니다.



인자 풀기(Unpacking)

*와 **는 함수를 정의할 때 인자를 묶는 역할을 하지만, 함수를 호출할 때는 반대로 묶여 있는 값을 풀어서 전달하는 역할을 합니다.

이미 목록이나 사전에 저장된 값을 함수에 전달할 때 유용합니다.


def func(*data, **method):
    num = sum(data) * method["scale"]
    print(num, method["unit"] + "입니다.")


numbers = (3, 4, 5)
options = {"scale": 10, "unit": "개"}

func(*numbers, **options)
결과
120 개입니다.

함수를 호출할 때도 *와 **를 사용할 수 있습니다.

*numbers는 튜플이나 리스트 같은 반복 가능한 객체의 요소를 위치 인자로 풀어서 전달합니다.

**options는 사전의 키(Key)와 값(Value)을 키워드 인자로 풀어서 전달합니다.

즉, func(*numbers, **options)는 func(3, 4, 5, scale=10, unit="개")와 같습니다.


def func(a, b, c):
    print(a, b, c)


try:
    func(*[1, 2])
except TypeError as e:
    print("TypeError:", e)

try:
    func(**{"a": 1, "b": 2, "d": 4})
except TypeError as e:
    print("TypeError:", e)

func(1, *[2], **{"c": 3})
결과
TypeError: func() missing 1 required positional argument: ‘c’
TypeError: func() got an unexpected keyword argument ‘d’
1 2 3

인자 풀기는 *args, **kwargs가 없는 일반 함수에도 사용할 수 있습니다.

다만, 풀어낸 결과가 함수의 매개변수와 맞아야 합니다. 요소가 부족하면 missing 오류가, 사전에 매개변수에 없는 키가 있으면 unexpected keyword argument 오류가 발생합니다.

마지막 줄처럼 일반 인자와 인자 풀기를 섞어 사용할 수도 있습니다.



위치 전용 매개변수(Positional-Only Parameter)

일반 매개변수는 위치로도, 키워드로도 값을 받을 수 있습니다.

이는 편리하지만, 함수를 만든 쪽에서는 매개변수의 이름도 호출 방식의 일부가 되어 이름을 자유롭게 바꿀 수 없다는 뜻이기도 합니다.

위치 전용 매개변수는 이름으로 전달하는 것을 막아, 매개변수의 이름을 함수 내부의 일로 만듭니다.


def func(a, b, /, c, *, d):
    print(a, b, c, d)


func(1, 2, 3, d=4)
func(1, 2, c=3, d=4)

try:
    func(a=1, b=2, c=3, d=4)
except TypeError as e:
    print("TypeError:", e)
결과
1 2 3 4
1 2 3 4
TypeError: func() got some positional-only arguments passed as keyword arguments: ‘a, b’

Python 3.8 이상부터 매개변수 목록에 슬래시(/)를 작성해 위치 전용 매개변수(Positional-Only Parameter)를 정의할 수 있습니다.

/보다 앞에 있는 매개변수는 위치로만 전달할 수 있으며, a=1처럼 키워드로 전달하면 TypeError가 발생합니다.

/와 * 사이의 매개변수는 위치나 키워드 어느 방식으로도 전달할 수 있고, * 뒤의 매개변수는 키워드로만 전달할 수 있습니다.

정리하면 def func(위치 전용, /, 위치 또는 키워드, *, 키워드 전용)의 구조가 됩니다.

위치 전용 매개변수는 매개변수의 이름을 바꿔도 호출하는 코드가 깨지지 않으므로, 이름이 중요하지 않은 매개변수에 사용합니다.

예를 들어, len(obj)의 obj처럼 이름에 특별한 의미가 없고 순서만으로 충분히 이해되는 매개변수가 해당됩니다.

또한, 위치 전용 매개변수의 이름은 **kwargs의 키와 겹쳐도 됩니다.


def func(value, /, **kwargs):
    print(value, kwargs)


func(1, value=2)
결과
1 {‘value’: 2}

value는 위치 전용이므로 value=2는 매개변수 value가 아닌 **kwargs로 전달됩니다.

/가 없다면 value=2가 매개변수 value에 할당되려 하므로 got multiple values for argument 'value' 오류가 발생합니다. 위치 전용으로 만들면 어떤 이름의 키워드 인자든 **kwargs로 받을 수 있습니다.

  • Tip : len(), abs(), divmod() 등 많은 내장 함수의 매개변수가 위치 전용입니다. help(len)을 실행하면 len(obj, /)처럼 /가 표시됩니다.



정리

이름 정의 / 호출 형태 특징
위치 인자 func(1, 2) 순서대로 매개변수에 할당
키워드 인자 func(a=1, b=2) 이름으로 할당, 순서 무관, 위치 인자 뒤에 작성
기본값 def func(a, b=2) 정의할 때 한 번만 생성, 변경 가능한 객체는 None 사용
*args def func(*args) 남은 위치 인자를 튜플로 받음
**kwargs def func(**kwargs) 남은 키워드 인자를 사전으로 받음
인자 풀기 func(*목록, **사전) 요소를 위치 인자 / 키워드 인자로 풀어서 전달
위치 전용 def func(a, /) 위치로만 전달 (3.8 이상)
키워드 전용 def func(*, a) 키워드로만 전달


매개변수는 위치 전용, /, 일반, *args, 키워드 전용, **kwargs 순서로 작성하며, 호출할 때는 위치 인자를 키워드 인자보다 앞에 작성합니다.

*args와 **kwargs는 개수가 정해지지 않은 인자를 받거나 다른 함수에 인자를 그대로 전달할 때 사용하고, 받을 인자가 정해져 있다면 매개변수를 직접 정의하는 편이 읽기 쉽고 오타도 방지할 수 있습니다.

댓글 남기기