Effective Python Item 29. 대입식을 사용해 컴프리헨션 안에서 반복작업을 피해라.

    들어가기 전

     


    요약

    • 왈러스 연산자를 이용해 컴프리헨션에서 반복 작업을 제거할 수 있음. 이를 통해 가독성 + 성능 향상이 가능함. 
    • 컴프리헨션에서 왈러스 연산자를 통해서 선언된 변수는 컴프리헨션 외부 Scope으로 노출됨. 이 부분을 고려해야함. (다른 변수를 오염시킨다거나 하는 부분)

     

     


    Item 29. 왈러스 연산자를 이용해 컴프리헨션 안에서 반복 작업을 피해라. 

    컴프리헨션에서 같은 계산을 여러 위치에서 공유해서 사용하는 경우가 굉장히 흔하다. 이런 경우에는 반드시 왈러스 연산자를 이용해서 반복된 작업을 줄여줘야한다. 그 이유는 다음과 같다.

    1. 변경에 취약하다. 
      • 계산하는 방법을 바꾸는 경우, 컴프리헨션 전체에 동일하게 적용해줘야 하는데 빼먹을 수 있음. 
    2. 코드가 읽기 어려워짐. 

    왈러스 연산자를 사용하면 위 단점을 모두 개선할 수 있다. 따라서 필요하다면 컴프리헨션 내부에서 왈러스 연산자를 사용하는 것이 좋다. 그러나 왈러스 연산자를 사용할 경우 한 가지 단점이 존재하한다. 컴프리헨션에서 왈러스 연산자로 선언된 표현식 자체가 컴프리헨션 외부 Scope로 노출될 수 있다는 점이다.

    # 컴프리헨션에서 왈러스 연산자를 사용하면, 변수가 외부 Scope으로 유출됨.
    # 컴프리헨션 내부의 조건식 / 일반식에서도 동일하게 노출됨.
    
    stock = {'a': 10, 'b': 20}
    
    half = [(last := count // 2) for count in stock.values()]
    print(f'{last=}')
    
    half2 = [count for count in stock.values() if (last2 := count // 2)]
    print(f'{last2=}')
    
    >>>
    last=10
    last2=10

    예를 들면 위 경우, last와 last2 모두 외부 Scope에 노출된 것을 확인할 수 있다. 따라서 이 부분을 반드시 잘 고려해서 코드를 작성해야한다. 

     


    코드 보기

    def get_batches(count, size):
        return count // size
    
    stock = {
        'a': 125,
        'b': 35,
        'c': 8,
        'd': 24,
    }
    
    order = ['a', 'b', 'c']
    
    # 첫번째 경우는 리스트 컴프리헨션에서 2개의 식으로 더 명확히 표현할 수 있음.
    result = {}
    for name in order:
        count = stock.get(name, 0)
        batches = get_batches(count, 8)
        if batches:
            result[name] = batches

    이 경우를 살펴보자.

    1. 창고에는 물건이 있고, order는 특정 물건에 대한 주문을 의미한다.
    2. 주문을 살펴보고, 적절한 재고가 있으면 주문에 대한 처리 결과를 result에 반환한다. 

    위 코드 자체는 for 문으로 되어있고 꽤 길기 때문에 컴프리헨션을 이용해 줄일 수 있는 여지가 많아 보인다.

    result = {name: get_batches(stock.get(name, 0), 8)
              for name in order if get_batches(stock.get(name, 0), 8)}

    딕셔너리 컴프리헨션으로 위 코드를 다음과 같이 줄일 수 있다. 여기서 문제는 다음과 같다.

    1. get_batches()가 두번 실행됨. → 수정이 어려움.
    2. 코드가 읽기 어려움. 

    get_batches()가 두 번 실행되고 있기 때문에 실수하기 쉬운 코드가 된다. 만약 내가 get_batches()에 8 대신 12를 주고 싶다고 했을 때, 두 군데 모두 수정을 해야 정상적으로 코드가 동작한다. 실수하게 되면 '찾기 어려운 버그'가 될 것이다. 왜냐하면 어떠한 에러도 발생하지 않기 때문이다.  이 부분을 왈러스 연산자를 이용해 해결할 수 있다.

    # 왈러스 연산자를 이용해, get_batches()를 줄임. 따라서 실수할 가능성도 줄이고, 가독성을 더 올림.
    result1 = {name: batches
               for name in order if (batches := get_batches(stock.get(name, 0), 8))}

    조건식에 왈러스 연산자를 이용해 batches라는 변수를 선언해서 처리했다. get_batches()를 수정하고 싶으면 이제는 한 곳만 수정하면 되기 때문에 코드가 좀 더 안정화 되었다고 볼 수 있다. 

    그러나 아직까지 get_batches() 내부에서 stock.get()을 호출하기 때문에 읽기 쉬운 코드는 아니다. 왈러스 연산자와 별개로 이 부분까지 개선시키고 싶다면 아래를 참고하면 된다.

    # stock.get() 자체가 노이즈를 가져오기 때문에 이를 개선한 버전임.
    name_count = [(name, stock.get(name, 0)) for name in order]
    result2 = {name: batches for name, count in order if (batches := get_batches(count, 8))}
    

    댓글

    Designed by JB FACTORY