vary 많이 보는 단어다. 중학교 때 영어 단어집에서도 본 것 같다. 다양하다 라고 적혀있었나.. 맥락에 따라 '변하다' 라고 충분히 해석 가능할 것이다. 비슷하게 생겼으니 variable 에 변수(변하는 수) 라는 의미를 주는 것은 매우 당연해 보인다.
variable-length arguments 를 의역하자면 '인자의 개수가 정해지지 않고 변하는 인자' 일 것이다.(사실 나는 argument 를 인자라고 부르는 것이나 보는 것에 어색하다 한자라서 그런가.. 그냥 arg 로 쓰겠다)
말 그대로 함수가 받는 arg 의 개수가 아직 정해지지 않았을 때, 혹은 모를 때 함수가 받는 arg 의 개수는 변한다.
args 는 arg의 위치에 따라 처리되는 positional 과 argName= 으로 처리되는 named(keyword) 두 종류가 있다. 둘 모두 함수를 선언할 때 args 개수를 정하지 않고, 함수를 호출 할 때 여러 개 입력하는 방법이 있다.
먼저 positional variable-length args 를 살펴보자. 파이썬에서는 *args 로 나타낸다.
다음의 최솟값을 찾는 함수를 보자.
def min_finder(num1, num2):
result = num1
if num2 < result:
result = num2
return result
arg 의 개수가 2 개 일 때 최솟값을 찾는 함수다.
arg 를 3 개 이상 받았을 때, 최솟값을 찾으려면?
list 를 이용해보자.
def min_finder(nums):
result = nums[0]
for num in nums:
if num < result:
result = num
return result
min_finder([3, 4, 5, 6, 2])
# output 2
이렇게 하면 2 개 이상의 args 를 넣을 수 있지만, 매번 숫자들을 list 에 넣어야 한다. 수의 개수가 10 개 미만 일 때는 괜찮을 수 있지만, 넘어가면 귀찮아진다. 또 다른 문제는, 애초에 list 에 넣을 숫자가 정해지지 않았다면 어떡해야 하는 걸까? 이럴 때 *args 를 쓰면 된다.
def min_finder(*args):
result = nums[0]
for num in nums:
if num < result:
result = num
return result
min_finder(1,2,3,4,5,6,7,8,0)
# output 0
살펴볼 점들이 몇가지 있다. 우선 *args 말고 *nums 로 써도 된다. * 뒤에 마음대로 써도 되지만, *args 로 쓰는게 관습이다. 다른 사람이 봤을 때, positional variable-length args 가 쓰였다는 것을 명확히 하기 위해서이다.
또 다른 중요한 점은 *args 뒤에는 positional arg 를 사용 하면 안된다. 반드시, named-args(e.g. nameOfArg=age) 를 이용해야 한다. (안 그러면 에러남)
함수 호출시 keyword arg 를 보면 positional arg 는 인자에서 이제 안나온다고 생각하자.
마지막으로, *args 의 요소에 접근할 때, list 처럼 접근하는 것처럼 보인다. 실제로는 *args 는 함수 scope 에서 tuple 이다.
def type_of_varLenArgs(*args):
print(type(args))
type_of_varLenArgs(1,2):
# output <class 'tuple'>
*args 는 postitonal args 로 처리될 때, 여러 개의 args 를 사용하거나 아직 정해지지 않았을 때 사용된다. 그러면 같은 경우에 keyword args 도 동일하게 처리하는 방법이 있을까? 당연히 있고 파이썬에서는 **kwargs 로 나타낸다.
여기서는 먼저 **args 의 type 부터 살펴보자.
def type_of_kwargs(**kwargs):
print(kwargs)
type_of_kwargs(first="Sam", last="Lee")
# output {"first": "Sam", "last": "Lee"}
** args 는 dictionary 형태이다!
다음 어떤 블로그의 post 데이터를 받아들이는 함수를 생각해보자.
def save_blog_data(author, content, tags=[], categories=[]):
pass
blog post 의 author 와 content 는 항상 있지만 tags 와 categories data 는 없는 경우도 있을 것이다. **kwargs 를 써서 묶어보자.
def save_blog_data(author, content, **kwargs):
if kwargs.get('tags'):
# 포스트 tags 데이터가 있다면 저장
pass
if kwargs.get('categories'):
# 포스트 catagories 데이터가 있다면 저장
pass
자, 이제 positional, named args 의 variable-length 경우를 모두 이해했다. 합쳐서 이용해보자.
def combined_varArgs(*args, **kwargs):
print(args)
print(kwargs)
combined_varArgs(3,2,1, name="Sam")
# output
(3, 2, 1)
{'name': 'Sam'}
positional args 와 named args 를 같이 쓸 때는 positional args 가 반드시 named args 보다 먼저 와야 한다. 또한, 고정된 길이의 args 와 variable-length args 를 같이 쓸 때는, 고정된 길이의 args가 먼저 와야 한다. 이로부터 함수의 args 를 넣을 때, 다음의 규칙이 만들어진다.
모두 섞어보자.
def combined_all (num1, num2, *args, author="Sam", **kwargs):
if kwargs.get(...):
...
pass
여기까지 이해한 내용을 바탕으로 다음의 간단한 문제들을 풀어보자.
1. 함수를 정의할 때 default value parameter 를 non-default value parameter 앞에 정의하면 오류가 나도록 설정된 이유
만약 default value 를 가지는 parameter 가 앞에 나오는 것을 허용한다면, 함수를 calling 할 때 어떤 arg에 default value가 적용되는지 혼란스러울 것이기 때문이다. args 가 많아 지면, 덮어씌워지는 등 많이 헛갈릴 것이다.
def fullName(first="Sam", last):
print('Lee')
# 내가 원하는 값은 Sam Lee 이지만 first 에
# Lee 가 덮어씌워져서 first='Lee' 가 되고 last 값은 indefinite 됨
def fullName(first, last="Lee"):
print("Sam")
# Much Better!
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("정우성", "20", mobile="01012341234", address="seoul")
# Fix! 알고있는 keyword agr 가 **kwargs 보다 뒤에 쓰였다. 앞으로 바꿔주자.
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("정우성", "20", address="seoul", mobile="01012341234")
# output
name= 정우성
age= 20
address= seoul
{'mobile': '01012341234}
3. 다음 함수가 호출되도록 에러를 고치기
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("정우성", "01012341234", "seoul", 20)
# 알고있는 positional arg 인 age 가 *args 보다 뒤에 쓰였다.
# 앞으로 옮겨주자
def func_param_with_var_args(name, age, *args):
print("name=",end=""), print(name)
print("args=",end=""), print(args)
print("age=",end=""), print(age)
func_param_with_var_args("정우성", 20, "seoul", "01012341234")
# output
name= 정우성
args= ('seoul', '01012341234')
age= 20
4. 다음 함수가 호출되도록 에러를 고치기
def mixed_params(name="아이유", *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, "정우성", "01012341234", "male" ,mobile="01012341234", address="seoul")
# args 순서를 규칙대로 바꿔주자
# 두 가지 경우가 있다. 1) name을 알려진 keyword arg 로 쓸건지,
# 아니면 2) positional arg로 쓰면서 name 의 default value 로
# "아이유" 를 쓸건지
1)
def mixed_params(age, address, *args, name="아이유", **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)
# address 는 positional 이므로 함수를
# 호출할 때 keyword 형태로 쓰면 error 가 난다. 주의해야한다.
mixed_params(20, "seoul", "정우성", "01012341234", "male", mobile="01012341234")
# output
name= 아이유
args= ('정우성', '01012341234, 'male')
age= '20'
kwargs= {'mobile': '01012341234'}
address= seoul
2)
def mixed_params(age, address, name="아이유" *args, **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, "seoul", "정우성", "01012341234", "male", mobile="01012341234")
# output
name= 정우성, # default 값인 '아이유' 에 '정우성' 이 덮어 씌어짐
args= ('01012341234', 'male')
age= 20
kwargs= {'mobile': '01012341234'}
address= seoul