위치인자(postional argument)를 가변적으로 받을 수 있으면 함수 호출이 깔끔해지고 시각적 잡음이 줄어든다.
이런 위치인자를 가변인자(varargs) 또는 스타인자(star args)라고 부르기도 한다.
예를들어 디버깅 정보를 로그에 남기고 싶다고 하자
def log(message: str, values: list[object]):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print(f"{message}: {values_str}")
log('내 숫자는 ',[1, 3, 5])
log('내 숫자는 ',[])
>>>
내 숫자는 : 1, 3, 5
내 숫자는
이 경우 로그에 남김 값이 없을 떄도 빈 리스트를 넘겨야 한다는 귀찮음과 시각적인 잡음이 있다.
이럴떄 함수 실행시 두번쨰 인자를 완전히 생략하면 좋을것이다. 파이썬에서는 마지막 위치 인자 이름 앞에 *를 붙이면 된다. 로그 메시지의 첫번째 파라미터는 반드시 필요하지만, 그 이후 모든 위치 인자는 선택사항 이기 떄문에 가변 인자를 써도 함수 본문은 바뀌지 않는다. 단지 함수 호출코드만 바뀔 뿐이다.
def log(message: str, *values: list[object]): # values 변수앞에 *이 추가됨
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print(f"{message}: {values_str}")
log('내 숫자는 ',[1, 3, 5])
log('내 숫자는 ') # 시각적으로 훨씬 좋다.
>>>
내 숫자는 : 1, 3, 5
내 숫자는
이는 함수를 호출하는 쪽에서 제네레이터 앞에 *연산자를 사용하면 제네레이터의 모든 원소를 얻기 위해 반복한다는 뜻이다.
이렇게 만드어지는 튜플은 제네레이터가 만들어낸 모든 값을 포함하며, 이로 인해 메모리를 아주 많이 소비하거나 프로그램이 중단될 수 있다.
def my_generator():
for i in range(10):
yield i
def my_func(*args):
print(args)
it = my_generator()
my_func(*it)
>>>
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
*args 인자를 받는 함수는 인자목록에서 가변적인 부분에 들어가는 인자의 개수가 처리하기 좋을 정도로 충분히 작다는 사실을 이미 알고 있는 경우 가장 적합하다.
args는 여러 리터럴이나 변수 이름을 함께 전달하는 함수 호출에 가장 이상적이다.
args는 주로 프로그래머의 편의와 코드 가독성을 위한 기능이다.
이미 가변인자가 존재하는 함수 인자 목록의 앞부분에 위치 인자를 추가하려고 시도하면, 기존 호출 코드를 변경하지 않는 경우 호출하는 코드가 미묘하게 꺠질 수도 있다.
def log(sequence, message: str, *values: list[object]): # values 변수앞에 *이 추가됨
if not values:
print(f'{sequence} - {message}')
else:
values_str = ', '.join(str(x) for x in values)
print(f"{sequence} - {message}: {values_str}")
log(1, '좋아하는 숫자는',7, 33 )
log(1, '안녕') # 새 코드에서 가변인자 없이 메시지만 사용
log('좋아하는 숫자는', 7, 33 ) # 예전 코드는 꺠진다.
>>>
1 - 좋아하는 숫자는: 7, 33
1 - 안녕
좋아하는 숫자는 - 7: 33
이 코드에서 세번쨰 log 호출시에는 sequence가 주어지지 않았기 떄문에 8을 message 로 사용한다는 점이 문제가 된다. 예외가 발생하지 않기때문에 에러 추적이 어렵다.
이런 가능성을 없애려면 *args를 받아들이는 함수를 확장할 떄는 키워드 기반의 인자만 사용해야한다. 키워드로만 인자를 지정하게 해서 함수 호출을 명확하게 만들수 있다.