Effective Python Item 19. 함수가 여러 값을 반환하는 경우 절대로 네 값 이상을 언패킹하지 마라.

    들어가기 전

    이 글은 이펙티브 파이썬 아이템 19를 공부하며 작성한 글입니다. 


    요약

    • 반환값이 많아지면 가독성이 떨어지며, 실수할 여지가 많아진다. 
    • 언패킹을 이용하거나, 언패킹 해야할 값이 너무 많아지면 데이터 클래스등으로 묶어서 반환해라. 

     


    Item 19. 함수가 여러 값을 반환하는 경우 절대로 네 값 이상을 언패킹하지 마라.

    # 슬라이싱보다는 언패킹을 이용하라. 
    lengths = [1, 2, 3, 4]
    
    
    def get_avg_ratio(numbers: list[int]) -> list[num]:
        average = sum(numbers) / len(numbers)
        scaled = [x / average for x in numbers]
        scaled.sort(reverse=True)
        return scaled
    
    # 언패킹을 이용하라.
    longest, *middle, shortest = get_avg_ratio(lengths)

    위의 코드에서 get_avg_ratio()는 정수 리스트를 반환한다. 이 정수 리스트는 정렬이 되어있고, 리스트에서 각 위치마다 값의 의미가 다르다. 

    예를 들어 함수의 반환 값을 num_list에 저장하고, num_list[0]으로 접근한다면 어떤 의미인지 이해할 수 있을까? 지금 당장은 이해할 수 있어도 추후에는 이해할 수 없다. 이 부분을 보완하기 위해서 슬라이싱을 이용하는 것보다 언패킹을 이용하는 것이 더 좋다. 

    # 언패킹으로 너무 많은 반환값을 받을 때 
    numbers = [1,2,3,4,5]
    def get_stats(numbers) -> tuple(int, int, int, int, int):
        maximum, minimum, avg, med, count = numbers
        return maximum, minimum, avg, med, count
    
    # 제대로 된 값을 받았을 때
    maximum, minimum, avg, med, count = get_stats(numbers)
    
    # 잘못 사용했을 때. -> 같은 타입이라 값이 바뀌기 딱 좋다.
    count, minimum, avg, med, maximum = get_stats(numbers)

    그런데 언패킹을 이용하다가 반환하는 값이 많아지는 경우가 있다. 반환하는 값이 많아지는 경우에는 어떤 문제점이 있을까? 

    같은 타입(int)의 값을 동시에 5개를 반환한다. 반환 값의 순서를 헷갈릴 경우가 많아질 것이고, 만약 여기서 실수하게 된다면 상당히 잡아내기 어려운 버그가 만들어진다.  위에서 count, maximum의 순서가 바뀌었는데 컴파일 에러도 없으며, 런타임에서도 잡아내기가 쉽지 않다. 

    # 줄바꿈도 가지각색이라 가독성이 안 좋음.
    count, minimum, avg, med, maximum \
        = get_stats(numbers)
    
    # 뭐가 읽기 편한가? 어떤 것도 읽기 편하지 않다. 
    (count, minimum, avg, med, maximum) = get_stats(
        numbers)

    또한 PEP 스타일에 따라 코드를 작성하면, 위 코드는 길이가 길기 때문에 줄바꿈을 할 것을 요구한다. 이 때, 줄바꿈을 하는 방법이 여러가지가 있기 때문에 가독성 자체가 나빠진다는 단점이 있다. 


    많은 반환 값의 해결방법

    반환값이 많은 함수를 개선하면 단단한 코드를 작성할 수 있다. 이 부분은 어떻게 개선해야할까? 아래 세 가지 정도 방법으로 개선해 볼 수 있다.

    1. 언패킹할 때 값이나 변수를 세 개까지만 사용하라. 
    2. 슬라이싱보다는 가급적이면 언패킹을 이용해서 처리하라.
    3. 더 많은 값을 언패킹 해야하는 경우 dataclass, namedTuple 같은 것을 사용하고 반환하게 하라.
    numbers = [1,2,3,4,5]
    
    @dataclass
    class ReturnValue:
        maximum: int
        minimum: int
        avg: int
        med: int
        count: int
    
    def get_stats(numbers: list[int]) -> ReturnValue:
        maximum, minimum, avg, med, count = numbers
        return ReturnValue(maximum, minimum, avg, med, count)
    
    ReturnValue = get_stats(numbers)
    
    # 필드로 접근할 수 있음.
    print(ReturnValue.maximum)

    위는 반환 값들을 하나의 경량 클래스(자바의 Record 처럼)로 묶은 결과를 보여준다. 

    • dataclass에서는 타입 힌트를 줄 수 있어 타입체커를 활용할 수 있음. 
    • dataclass에서는 각 필드별로 접근 가능하기 때문에 순서를 헷갈릴 수 없음. 
    • 반환값이 적어지므로 언패킹 시, 띄어쓰기에 대한 부담감이 줄어듦. 

     

     

     

     

    댓글

    Designed by JB FACTORY