betterway16에러보다는get사용

김승환·2021년 7월 11일

코딩의 기술

목록 보기
7/36

in을 사용하고 딕셔너리 키가 없을 때 KeyError를 처리하기보다는 get을 사용하라

  • 딕셔너리 상호작용 세가지
    - 접근, 대입, 삭제
  • 딕셔너리는 동적으로 어떤 키에 접근하거나 키를 삭제할 때 그 키가 딕셔너리에 없을 수도 있음
# ex) 샌드위치 가게에서 고객들이 가장 좋아하는 빵을 찾아 메뉴를 결정하고 싶다.
# 각사람이 얼마나 투표했는지 저장한 딕셔너리를 정의
counters = {
    '품퍼니켈': 2,
    '사워도우': 1,
}
  • 투표가 있을 때 카운터를 증가시키는 함수
  • 키가 없으면 새로운 값을 디폴트0으로 넣고 한번의 투표가 있기 때문에 +1을 해준다.
# if문을 사용한 예시
key = '밀'
print(key in counters)
if key in counters:
    count = counters[key]
else:
    count = 0

counters[key] = count + 1
print(counters[key])
print(counters) 

False
1
{'품퍼니켈': 2, '사워도우': 1, '밀': 1}

#try를 사용한 예시
counters = {
    '품퍼니켈': 2,
    '사워도우': 1,
}
key = '밀'
print(key in counters)
try:
    count = counters[key]
except KeyError:
    count = 0

counters[key] = count + 1

print(counters[key])
print(counters) 

False
1
{'품퍼니켈': 2, '사워도우': 1, '밀': 1}

# get인자를 통해 호출했을 때 없는 경우 0이라는 값을 호출
count = counters.get(key, 0) #두번째 인자가 없을때 어떤값을 호출할지 설정함.
counters[key] = count + 1
print(counters[key])
print(counters) 

2
{'품퍼니켈': 2, '사워도우': 1, '밀': 2}

# in과 error를 사용했을 경우 더 짧지만 가독성은 떨어진다.

if key not in counters:
    counters[key] = 0

counters[key] += 1

if key in counters:
    counters[key] += 1
else:
    counters[key] = 1

try:
    counters[key] += 1
except KeyError:
    counters[key] = 1
    
print(counters[key])
print(counters) 

5
{'품퍼니켈': 2, '사워도우': 1, '밀': 5}

결과적으로 get함수를 사용하는 것이 가장 코드가 짧고 깔끔하다.

딕셔너리에 저장된 값이 리스트인 경우

  • 어떤 사람이 어떤 유형의 빵에 투표했는지 알고 싶을 경우
  • 위 경우는 각 키마다 이름 리스트를 연관시킬 수 있다.
votes = {
    '바게트': ['철수', '순이'],
    '치아바타': ['하니', '유리'],
}
print(votes)

{'바게트': ['철수', '순이'], '치아바타': ['하니', '유리']}

key = '브리오슈'
who = '단이'

if key in votes:
    names = votes[key]
else:
    votes[key] = names=[]  #name을 리스트로 저장[] 처음에 폴더를 생성할 필요없이 한줄로 사용가능

names.append(who)
print(votes)

{'바게트': ['철수', '순이'], '치아바타': ['하니', '유리'], '브리오슈': ['단이']}

# keyerror예외가 발생한다는 사실에 의존한 함수
# in 보다는 효율적이다.

try:
    names = votes[key]
except KeyError:
    votes[key] = names = []

names.append(who)
print(votes)

{'바게트': ['철수', '순이'], '치아바타': ['하니', '유리'], '브리오슈': ['단이', '단이']}

#get함수를 사용하면 보다 간단해진다.

names = votes.get(key)
if names is None:
    votes[key] = names = []

names.append(who)
print(votes)

{'바게트': ['철수', '순이'], '치아바타': ['하니', '유리'], '브리오슈': ['단이', '단이', '단이']}

# betterway10에서 배운 월러스를 이용하면 더 간단해진다.

if (names := votes.get(key)) is None:
    votes[key] = names = []

names.append(who)
print(votes)

{'바게트': ['철수', '순이'], '치아바타': ['하니', '유리'], '브리오슈': ['단이', '단이', '단이', '단이']}

votes.get('바게트',0)

['철수', '순이']

dict은 setdefault메서드를 제공

  • setdefault는 딕셔너리 키를 사용해 값을 가져오려고 시도
  • setdefault는 키가 없으면 제공받은 디폴트 값을 키에 연관시켜 딕셔너리에 대입한 다음 키에 연관된 값을 반환
names = votes.setdefault(key, [])
names.append(who)
print(votes)

{'바게트': ['철수', '순이'], '치아바타': ['하니', '유리'], '브리오슈': ['단이', '단이', '단이', '단이', '단이', '단이']}

setdefault는 get보다 효율적이지만 가독성이 좋지 못하다.

  • 함수 이름으로 자신의 역할을 설명하기 부족하기 때문
    setdefault의 함정 : 키가 없으면 함수에 전달된 디폴드 값이 따로 복사되지 않고 딕셔너리에 직접 대입.
  • 호출할 때마다 리스트를 만들어야 하는 번거로움
  • 가독성과 효율성을 향상시키고자 디폴트 값에 사용하는 객체를 재활용한다면 이상한 동작을 하고 버그 발생
# setdefault의 함정 예시
# 따로 키를 설정하지 않아도 빈 값에 대입이 된다.
data = {}
key = 'foo'
value = []
data.setdefault(key, value)
print('이전:', data)
value.append('hello')
print('이후: ', data)

이전: {'foo': []}
이후: {'foo': ['hello']}

최초 예제에 setdefault를 사용하지 않은 이유

  • 카운터를 증가 시키면 항상 딕셔너리에 저장해야한다. 그렇기때문에 디폴트값의 대입은 불필요
    setdefault를 사용하는 것이 딕셔너리 키를 처리하는 지름길인 경우는 드물다
  • ex) 디폴트 값을 만들어내기 쉽거나, 디폴트 값이 변경 가능한 값이거나, 리스트 인스턴스 처럼 값을 만들어 낼 때 예외가 발생할 가능성이 없는 경우 setdefault를 사용할 수 있다.
    위 상황에서도 실제로는 defaultdict를 사용하는 것으로 충분할 수 있다. (betterway17에 나옴..)
profile
인공지능 파이팅!

0개의 댓글