ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] Tip - 가변 위치 인수로 깔끔하게 코딩
    언어/파이썬 & 장고 2016. 10. 16. 18:11

    선택적인 위치 인수 ( 이런 파라미터 이름을 관례적으로 *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를 받는 함수에 새 위치 파라미터를 추가하면 찾기 어려운 버그가 생길 수 있음

    댓글