betterway29 대입식

김승환·2021년 7월 11일

코딩의 기술

목록 보기
20/36

대입식을 사용해 컴프리헨션 안에서 반복 작업을 피하라

컴프리헨션에서 같은 계산을 여러 위치에서 공유하는 경우가 흔하다.

  • 예를 들어 파트너 회사에서 주문을 관리하기 위한 프로그램을 작성한다고 하자.
  • 고객이 새로운 주문을 보내면 주문을 처리할 만한 재고가 있는지 알려줘야 한다.
  • 그러려면 고객의 요청이 재고 수량을 넘지 않고, 배송에 필요한 최소 수량을 만족하는지 확인해야 한다.
stock = {
    '못': 125,
    '나사못': 35,
    '나비너트': 8,
    '와셔': 24,
}

order = ['나사못', '나비너트', '클립']

def get_batches(count, size):
    return count // size

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

print(result)

{'나사못': 4, '나비너트': 1}

# 딕셔너리 컴프리헨션을 사용하면 더 간결해 진다.

#
found = {name: get_batches(stock.get(name, 0), 8)
         for name in order
         if get_batches(stock.get(name, 0), 8)}
print(found)
  • 이 코드는 앞의 코드보다 짧지만 get_batches가 반복된다는 단점이 있다.
  • 기술적으로는 불필요한 시각적인 잡음이 들어가서 가독성이 나빠진다.
  • 두 식을 항상 똑같이 변경해야 하므로 슬수할 가능성도 높아진다.
#예를들어 get_batches 호출에서만 두 번째 인자를 8대신 4로 바꿨는데 결과가 달라진다.

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

print('예상:', found)
print('실졔: ', has_bug)

예상: {'나사못': 4, '나비너트': 1}
실졔: {'나사못': 8, '나비너트': 2}

# 위 상황 예시
# if문을 지워서 첫줄 함수도 작동을 하는지 확인
#if문은 key가없는 값은 실행을 안해주는 역할일 뿐
#그렇기 때문에 아래와 같이 나옴
has_bug = {name: get_batches(stock.get(name, 0), 4)
           for name in order}
print(has_bug)

{'나사못': 8, '나비너트': 2, '클립': 0}

위 문제의 간단한 해법은 왈러스 연산자를 사용

  • 왈러스 연산자를 이용하면 컴프리헨션의 일부분에 대입식을 만들 수 있다.
    -stock 딕셔너리에서 각 order 키를 한번만 조회하고 get_batches를 한번만 호출해서 그 결과를 batches 변수에 저장할 수 있다.
    - batches 변수를 참조해서 get_batches를 다시 호출할 필요없이 딕셔너리의 내용을 만들 수 있다.
    - get_batches를 얻기 위한 불필요한 함수 호출을 제거하면 order안에 각원소에 대해 불필요한 연산을 수행하지 않으므로 성능 향상도 된다.
# 왈러스를 이용
found = {name: batches for name in order
         if (batches := get_batches(stock.get(name, 0), 8))}
print(found)

{'나사못': 4, '나비너트': 1}

  • 대입식을 컴프리헨션의 값 식에 사용해도 문법적으로 올바르다.
  • 하지만 컴프리헨션의 다른 부분에서 이 변수를 읽으려고 하면 컴프리헨션이 평가되는 순서 때문에 실행 시점에 오류가 난다.
# 오류가 나는 부분. 오류를 보고 싶으면 커멘트를 해제할것
result = {name: (tenth := count // 10)
          for name, count in stock.items() if tenth > 0}

NameError Traceback (most recent call last)
in
1 # 오류가 나는 부분. 오류를 보고 싶으면 커멘트를 해제할것
----> 2 result = {name: (tenth := count // 10)
3 for name, count in stock.items() if tenth > 0}

in (.0)
1 # 오류가 나는 부분. 오류를 보고 싶으면 커멘트를 해제할것
2 result = {name: (tenth := count // 10)
----> 3 for name, count in stock.items() if tenth > 0}

NameError: name 'tenth' is not defined

대입식을 조건 쪽으로 옮기고 대입식에서 만들어진 변수 이름을 컴프리헨션 값 식에서 참조하면 이 문제 해결 가능

result = {name: tenth for name, count in stock.items()
          if (tenth := count // 10) > 0}
print(result)
print(tenth)

{'못': 12, '나사못': 3, '와셔': 2}
2

컴프리헨션 값 부분에서 왈러스 연산자를 사용할 때 그 값에 대한 조건 부분이 없다면 추프 밖 영역으로 루프 변수가 누출된다.

stock = {
    '못': 125,
    '나사못': 35,
    '나비너트': 8,
    '와셔': 24,
}
half = [(last := count // 2) for count in stock.values()]
print(f'{half}의 마지막 원소는 {last}')

[62, 17, 4, 12]의 마지막 원소는 12

이런 루프 변수 누출은 일반적인 for 루프에서 발생하는 루프 변수 누출과 비슷하다.

for count in stock.values(): # 루프 변수가 누출됨
    pass

print(f'{list(stock.values())}의 마지막 원소는 {count}')

[125, 35, 8, 24]의 마지막 원소는 24

  • 컴프리헨션 루프 변수인 경우에는 비슷한 누출이 없음
  • 원래는 없지만 왈러스를 사용하면 누출이 있으니까 조건문 안에 사용하라는 의미
stock = {
    '못': 125,
    '나사못': 35,
    '나비너트': 8,
    '와셔': 24,
}
half = [count // 2 for count in stock.values()]
print(half)  # 작동함
print(count) # 루프 변수가 누출되지 않기 때문에 예외가 발생함

[62, 17, 4, 12]
24

stock = {
    '못': 125,
    '나사못': 35,
    '나비너트': 8,
    '와셔': 24,
}
half=[]
for count in stock.values():
    print(count)
    half.append(count)
print('--------------')
print(count)

125
35
8
24
24

  • 누출이 없는 편이 낫다.
  • 대입식은 제너레이터 경우에도 똑같은 방식으로 작동한다.
# 예시는 딕셔너리 인스턴스 대신 제품 이름과 현재 재고 수량 쌍으로 이뤄진 이터레이터를 만든다.
stock = {
    '못': 125,
    '나사못': 35,
    '나비너트': 8,
    '와셔': 24,
}

order = ['나사못', '나비너트', '클립']

found = ((name, batches) for name in order
         if (batches := get_batches(stock.get(name, 0), 8)))
print(next(found))
print(next(found))

('나사못', 4)
('나비너트', 1)

기억할 내용

  • 대입식을 통해 컴프리헨션이나 제너레이터 식의 조건 부분에서 사용한 값을 같은 컴프리헨션이나 제너레이터의 다른 위치에서 재사용할 수 있다.
  • 이를통해 가독성과 성능을 향상시킬 수 있다.
  • 조건이 아닌 부분에도 대입식을 사용할 수 있지만, 그런 형태의 사용은 피해야한다.
profile
인공지능 파이팅!

0개의 댓글