[Python] Skill of coding - *args로 깔끔하게

가변위치 인수(*args)로 깔끔하게 보이게 하기

선택적인 위치 인수(이런 파라미터의 이름을 관례적으로 *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

로그로 남길 값이 없을 때 빈 리스트를 넘겨야 한다는 건 불편하고 성가신 일이다. 두 번째 인수를 아예 남겨둔다면 더 좋을 것이다. 파이썬에서 * 기호를 마지막 위치 파라미터 이름 앞에 붙이면 된다. 로그 메시지(log 함수의 message 인수)를 의미하는 첫 번째 파라미터는 필수지만, 다음에 나오는 위치 인수는 몇 개든 선택적이다. 함수 본문은 수정할 필요가 없고 호출하는 쪽만 수정하면 된다.

def log(message, *values):  # The only difference
    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',)  # Much better
My numbers are: 1, 2
Hi there

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

favorites = ['7']
favorites1 = (7,)
favorites2 = 7,33,99
favorites3 = {7,33,99}
log('Favorite colors',type(*favorites))
log('Favorite colors',type(*favorites1))
log('Favorite colors',*favorites2)
log('Favorite colors',favorites3)
Favorite colors: <class 'str'>
Favorite colors: <class 'int'>
Favorite colors: 7, 33, 99
Favorite colors: {33, 99, 7}

가변 개수의 위치 인수를 받는 방법에는 두 가지 문제가 있어요.

첫 번째

가변인수가 함수에 전달되기에 앞서 항상 튜플로 변환된다는 점이다. 이는 함수를 호출하는 쪽에서 제너레이터에 * 연산자를 쓰면 제너레이터가 모두 소진될 때까지 순회됨을 의미해요.

결과로 만들어지는 튜플은 제너레이터로부터 생성된 모든 값을 담으므로 메모리를 많이 차지해 결국 프로그램이 망가지게 할 수도 있어요.

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를 받는 함수는 인수 리스트에 있는 입력의 수가 적당히 적다는 사실을 아는 상황에서 가장 좋은 방법이에요. 이런 함수는 많은 리터럴이나 변수 이름을 한꺼번에 넘기는 함수 호출에 이상적이에요. 주로 개발자들은 편하게 하고 코드의 가독성을 높이려고 사용해요.

두 번째 문제

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

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)      # New usage is OK
log('Favorite numbers', 7, 33)  # Old usage breaks
1: Favorites: 7, 33
Favorite numbers: 7: 33

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

핵심 정리

  • def문에서 *args를 사용하면 함수에서 가변 개수의 위치 인수를 받을 수 있다.
  • 연산자를 쓰면 시퀀스에 들어 있는 아이템을 함수의 위치 인수로 사용할 수 있어요.
  • 제너레이터와 * 연산자를 같이 쓰면 프로그램이 메모리 부족으로 망가질수 있어요
  • *args를 받는 함수에 새 위치 파라미터를 추가하면 정말 찾기 어려운 버그가 생길 수 있어요.

좋은 웹페이지 즐겨찾기