선택적인 위치 인수 ( 이런 파라미터 이름을 관례적으로 *args라고해서 종종 'star args'라고도 함)를 받게 만들면 함수 호출을 더 명확하게 할 수 있고 보기에 방해가 되는 요소를 없앨 수 있습니다.

예를 들어 디버그 정보 몇 개를 로그로 남긴다고 할 때, 인수의 갯수가 고정되어 있다면 메시지와 값 리스트를 받는 함수가 필요합니다.

def log(message, values):
    if not values:
        print(message)
    else:
        values_str=', '.join(str(x) for x in values)
        print('%s: %s' % (message, values_str))


log('My numbers are',[1,2])
log('Hi there',[])


# 결과
# My numbers are: 1, 2
# Hi there


로그로 남길 값이 없을 때 빈 리스트를 넘겨야 한다는 건 불편하고 성가신 일입니다. 두 번째 인수를 아예 남겨둔다면 더 좋을 것입니다. 파이썬에서는 *기호를 마지막 위치 파라미터 이름 앞에 붙이면 됩니다. 

def log(message, *values):  # 유일하게 다른 부분
    if not values:
        print(message)
    else:
        values_str = ', '.join(str(x) for x in values)
        print('%s: %s' % (message, values_str))


log('My numbers are', 1, 2)
log('Hi there')


리스트를 log 같은 가변 인수 함수를 호출하는데 사용하고 싶다면 * 연산자를 쓰면 됩니다. 그러면 파이썬은 시퀀스에 들어 있는 아이템들을 위치 인수로 전달하게 됩니다.

이러한 가변 개수의 위치 인수를 받는 방법에는 두 가지 문제가 있습니다.

  1. 가변 인수가 함수에 전달되기에 앞서 항상 튜플로 변환
    • 함수를 호출하는 쪽에서 제너레이터에 *연산자를 쓰면 제너레이터가 모두 소진될 대까지 순회됨을 의미합니다. 결과로 만들어지는 튜플은 제너레이터로부터 생성된 모든 값을 담으므로 메모리를 많이 차지해 결국 프로그램이 망가질 수도 있습니다.

      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를 받는 함수는 인수 리스트에 있는 입력의 수가 적당히 적다는 사실을 아는 상황에서는 좋은 방법입니다. 

  2. 추후 호출 코드를 모두 변경하지 않고서는 새 위치 인수를 추가할 수 없음
    1. 인수 리스트의 앞쪽에 위치 인수를 추가하면 기존의 호출코드가 수정없이는 이상하게 동작합니다.

      def log(sequence, message, *values):
          if not values:
              print('%s: %s' % (sequence, message))
          else:
              values_str = ', '.join(str(x) for x in values)
              print('%s: %s: %s' % (sequence, message, values_str))
      
      
      log(1, 'Favorites', 7, 33) # 새로운 용법 문제 없음
      log('Favorite numbers: ', 7, 33) # 이전에 쓰던 용법은 제대로 동작하지 않음
      
      # 결과
      # 1: Favorites: 7, 33
      # Favorite numbers: : 7: 33

      이 코드의 문제는 두 번째 호출이 sequence 인수를 받지 못했기 때문에 7을 message 파라미터로 사용한다는 점입니다. 이런 버그는 코드에서 예외를 일으키지 않고 계속 실행되므로 발견하기가 어렵습니다. 이런 문제 발생 가능성을 완전히 없애려면 *args를 받는 함수를 확장할 때 키워드 전용 인수를 사용해야 합니다.

요약

def문에서 *args를 사용하면 함수에서 가변 개수의 위치 인수를 받을 수 있음

* 연산자를 쓰면 시퀀스에 들어 있는 아이템을 함수의 위치 인수로 사용할 수 있음

제너레이터와 *연산자를 함께 사용하면 프로그램이 메모리 부족으로 망가질 수 있음

*args를 받는 함수에 새 위치 파라미터를 추가하면 찾기 어려운 버그가 생길 수 있음