All Articles

Python - function 정의 시 지켜야 할 argument 순서

함수에서 미리 지정된 parameter는 그렇지 않은 것보다 반드시 뒤에 와야한다. 따라서 아래 코드를 실행하면 SyntaxError가 뜬다.

def order(no1 = "아침", no2, no3):
    print(f"{no1}은 9시에 {no2}은 12시에 {no3}은 6시에 먹는다.")
  
order("점심", "저녁")
# 출력값
SyntaxError: non-default argument follows default argument

아직 값이 없는 나머지 parameter에 순서대로 들어가면 될 것 같은데 왜 그럴까?


우선 파이썬 function parameter에서 arguments를 처리하는 순서는 다음과 같다.

  1. positional(non-default) arguments
  2. default arguments
  3. variable length positional arguments (=*arg)
  4. keyword-only arguments 4-1. non-default keyword-only arguments 4-2. default keyword-only arguments
  5. variable length keyword arguments (=**kwargs)

간단한 번역과 함께 예를 조금 들자면 이렇다.

  1. 앞에서부터 순서대로 지정되는 arguments
  2. 함수 정의 단계에서 parameter에 이미 지정된 arguments
  3. 순서대로 지정되며 개수가 가변적인 arguments (예 - list, tuple)
    4-1. 1번처럼 함수 정의 단계에서 값을 정하지 않는 arguments. 함수 호출 단계에서 변수="값" 형태로 argument를 준다.
    4-2. 2번처럼 함수 정의 단계에서 값을 지정하는 arguments. *(asterisk) 뒤에 오는지로 2번과 구분한다. 사실상 거의 쓸 일이 없다
  4. key = value 짝 개수가 가변적인 arguments (예 - dictionary)

이제 1번 positional arguments와 3번 variable length positional arguments를 조합해서 함수를 만들어보자.

def test(a, b, *arg):
    print(f"a는 {a}이고 b는 {b}고 variable length arguments는 {arg}입니다.")

test(1, 2, 3, 4, 5)
# 출력값
a는 1이고 b는 2고 variable length arguments는 (3, 4, 5)입니다.

위 함수의 동작 방식은 다음과 같다.

  • positional arguments인 a, b에 첫 번째, 두 번째 argument인 1과 2가 할당되었다.
  • 나머지는 튜플 형태로 arg argument에 할당되었다. variable length arguments는 위 예시와 같이 요소 3개짜리 튜플이 될 수도 있고 5개짜리가 될 수도 있다.
    참고) *(asterisk)는 list / tuple 을 해체해서 그 안의 요소를 하나씩 꺼내는 언패킹을 의미

만약 a, b와 arg의 위치가 반대라면?

def test(*arg, a, b):
# 코드 후략
# 출력값
TypeError: test() missing 2 required keyword-only arguments: 'a' and 'b'

주어진 arguments가 모두 가변 인자(*arg)에 들어가 a와 b는 받을 arguments가 없어졌다.


다음으로, 1번 positional arguments와 2번 default arguments와 4번 keyword-only arguments, 5번 variable length keyword arguments를 조합해보자.

def stafflist(name, area="서울", *, characteristic, **info):
    print(f"{name}씨는 {area}지역 {characteristic} 직원입니다.")
    print(f"사무실 주소는 {info['address']}입니다.")
    print(f"이메일 주소는 {info['email']}입니다.")
    print(f"연락처는 {info['phone']}입니다.")

info = {
    "김안녕" : {
        "address" : "서울시 강남구 테헤란로",
        "email" : "kim@email.com",
        "phone" : "01012341234"
    },
    "정헬로" : {
        "address" : "서울시 강남구 역삼로",
        "email" : "hello@email.com",
        "phone" : "01056785678"
    },
    "강하이" : {
        "address" : "서울시 강남구 영동대로",
        "email" : "hi@email.com",
        "phone" : "01043214321"
    }    
}

staffname = "김안녕"
stafflist(staffname, characteristic="성실한", **info[staffname])
# 출력값
김안녕씨는 서울지역 성실한 직원입니다.
사무실 주소는 서울시 강남구 테헤란로입니다.
이메일 주소는 kim@email.com입니다.
연락처는 01012341234입니다.

**(double asterisk)는 dictionary 을 해체해서 그 안의 key와 value를 하나씩 꺼내는 언패킹을 의미. key 한 번, value 한 번, 총 두 번 언패킹을 해야하기 때문에 애스터리스크를 두 번 붙인다. key만 사용하려면 한 번만 붙여준다.

위 함수의 동작 방식은 다음과 같다.

  • stafflist(staffname, **info[staffname])에서 staffname, 즉 "김안녕"은 parameter 중 name에 들어갔다.
  • area는 이미 지정했으므로 입력한 argument와 관계 없이 패스
  • characteristic 값을 argument로 지정해줬으므로 지정한 값이 들어간다.
  • **info에는 info[staffname], 즉 딕셔너리 info"김안녕" 키에 해당하는 값이 들어갔다.

만약 순서가 바뀌어 default argument가 positional argument보다 앞으로 오게 되면 SyntaxError가 뜰 것이다.

def stafflist(area="서울", name, *, characteristic, **info):
# 코드 후략
# 출력값
SyntaxError: non-default argument follows default argument

위에서 정리한 내용을 바탕으로 에러가 나는 구문을 수정해보자.

  • 상황 1
def func_param_with_var_args(name, *args, age):
    print("name=",end=""), print(name)
    print("args=",end=""), print(args)
    print("age=",end=""), print(age)

func_param_with_var_args("wecode", "01012341234", "seoul", 20)
# 출력값
TypeError: func_param_with_var_args() missing 1 required keyword-only argument: 'age'

*args"wecode"를 제외한 나머지 arguments를 모두 가져가 age는 아무것도 받지 못해 TypeError가 발생했다. 하지만 SyntaxError가 아닌 이상 함수 정의 단계에서는 문제가 없다는 뜻이다. 따라서 위 함수를 동작하게 하는 방법은 두 가지가 있다.

  1. 함수를 다시 정의한다.
    variable length argument인 *argsage 뒤에 보내 age를 positional argument로 취급해준다. 따라서 함수 호출 부분에서도 argument 순서를 바꿔주어야 한다.
def func_param_with_var_args(name, age, *args):
    print("name=",end=""), print(name)
    print("age=",end=""), print(age)
    print("args=",end=""), print(args)


func_param_with_var_args("wecode", 20, "01012341234", "seoul")
# 출력값
name=wecode
age=20
args=(01012341234, 'seoul')
  1. argument의 성질을 명확히 정의해준다.
    초기 TypeError가 난 함수의 문제는 positional argument인 age가 가져갈 값이 없다는 것이다. 따라서 variable length argument보다 뒤에 위치한 age를 keyword-only argument로 만들어서 값을 뺏기지 않도록 한다.
def func_param_with_var_args(name, *args, age):
    print("name=",end=""), print(name)
    print("args=",end=""), print(args)
    print("age=",end=""), print(age)


func_param_with_var_args("wecode", "01012341234", "seoul", "age"=20)
# 출력값
name=wecode
args=('01012341234', 'seoul')
age=20
  • 상황 2
def func_param_with_kwargs(name, age, **kwargs, address=0):
    print("name=",end=""), print(name)
    print("age=",end=""), print(age)
    print("kwargs=",end=""), print(kwargs)
    print("address=",end=""), print(address)


func_param_with_kwargs("wecode", "20", mobile="01012341234", address="seoul")
# 출력값
SyntaxError: invalid syntax

위 함수는 positional - positional - variable length keyword - default 순으로 정의되었다. 따라서,

def func_param_with_kwargs(name, age, address=0, **kwargs):
    print("name=",end=""), print(name)
    print("age=",end=""), print(age)
    print("address=",end=""), print(address)
    print("kwargs=",end=""), print(kwargs)


func_param_with_kwargs("wecode", "20", mobile="01012341234", address="seoul")
# 출력값
name=wecode
age=20
address=seoul
kwargs={'mobile': '01012341234'}

variable length keyword arguments를 가장 뒤로 보내주면 정상 출력된다. 함수 호출 단계에서 mobileaddress보다 먼저 나왔지만 키를 지정해주었기 때문에 순서와 상관없이 제자리를 찾아갔다.

  • 상황 3
def mixed_params(name="wecode", *args, age, **kwargs, address):
    print("name=",end=""), print(name)
    print("args=",end=""), print(args)
    print("age=",end=""), print(age)
    print("kwargs=",end=""), print(kwargs)
    print("address=",end=""), print(address)


mixed_params(20, "wecode", "01012341234", "male" ,mobile="01012341234", address="seoul")
# 출력값
SyntaxError: invalid syntax

default - variable length - positional - variable length keyword - keyword-only 순으로 함수를 정의해 SyntaxError가 났다. 따라서 positional - default - variable length - keyword-only - variable length keyword 순으로 배열해준다.

def mixed_params(age, name="wecode", *args, address, **kwargs):
    print("name=",end=""), print(name)
    print("args=",end=""), print(args)
    print("age=",end=""), print(age)
    print("kwargs=",end=""), print(kwargs)
    print("address=",end=""), print(address)


mixed_params(20, "wecode", "01012341234", "male" ,mobile="01012341234", address="seoul")
# 출력값
name=wecode
args=('01012341234', 'male')
age=20
kwargs={'mobile': '01012341234'}
address=seoul

참고한 자료