파이썬 코딩을 더 깔끔하게! #13

Dunno·2021년 7월 4일
0

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

딕셔너리와 상호작용하는 세 가지 기본 연산(Key와 Value에 접근, 대입, 삭제)을 수행할 때 동적인 딕셔너리의 내용으로 인해 키에 접근하거나 삭제할 때 그 키가 딕셔너리에 없을 경우가 종종 발생한다.

카운터로 이뤄진 딕셔너리

  • 샌드위치 가게의 고객들이 가장 좋아하는 빵에 대한 투표를 저장한 딕셔너리
counters = {
	'품퍼니켈' : 2,
	'사워도우' : 1,
}

위와 같은 딕셔너리에서 투표가 일어날 때 카운터를 증가시키려면 먼저 키가 딕셔너리에 존재하는지 살펴봐야 한다. 키가 없으면 디폴트 카운터 값인 0을 딕셔너리에 넣고 그 카운터를 증가시킨다.

  • if 문과 키가 존재할 때 참을 반환하는 in을 사용하는 방법
key = '밀'

if key in counters:
	count = counters[key]
else:
	count = 0

counters[key] = count + 1

→ 딕셔너리에서 키를 두 번 읽고, 키에 대한 값을 한 번 대입해야 한다, 비효율적이다.

  • 존재하지 않는 키에 접근할 때 발생하는 KeyError 예외를 활용하는 방법
try:
	count = counters[key]
except KeyError:
	count = 0

counters[key] = count + 1

→ 키를 한 번만 읽고 값을 한 번만 대입하면 되므로 in에 비해 효율적이나 get 메소드 활용에 비해 비효율적이다.

  • in식과 KeyError를 사용해 짧은 코드를 작성하는 방법
#방법 1
if key not in counters:
	counters[key] = 0
counters[key] += 1

#방법 2
if key in counters:
	counters[key] += 1
else:
	counters[key] = 1

#방법 3
try:
	counters[key] += 1
except KeyError:
	counters[key] = 1

→ 대입을 중복 사용해야 하므로 코드의 가독성이 떨어지므로 비효율적이다.

  • dict 내장 타입의 get 메서드 사용하는 방법
count = counters.get(key,0) 
#get의 두 번재 인자는 딕셔너리에 key가 없을 때 돌려줄 디폴트 값

counters[key] = count + 1

→ 키를 한 번만 읽고 값을 한 번만 대입한다, KeyError 방법에 비해 코드가 짧다. 가장 효율적이고 가독성을 높일 수 있는 방법이다.

  • collection내장 모듈의 Counter클래스

    카운터로 이뤄진 딕셔너리를 유지해야 하는 경우에는 collection 내장 모듈의 Counter클래스를 사용하는 것을 고려해보는 것도 좋을 것 같다.

딕셔너리에 저장된 값이 복잡한 경우

  • 어떤 사람이 어떤 유형의 빵에 투표했는지를 저장한 딕셔너리
votes = {
	'바게트' : ['철수', '순이'],
	'치아바타' : ['하니', '유리'],
}
  • in을 사용한 방법
key = '브리오슈'
who = '단이'

if key in votes:
	names = votes[key]
else:
	votes[key] = names = []

names.append(who)
print(votes)

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

→ 딕셔너리에서 키를 두 번 읽고, 키가 없는 경우 값을 한 번 대입하므로 비효율적이다.

  • votes[key] = names = []

    a = b = c 와 같은 형태의 이중 대입문은 a와 b가 c 값을 가진다는 의미이다.

    ex) a = b = c = 2

    a=2, b=2, c=2

  • KeyError 예외를 활용하는 방법

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

names.append(who)

→ 키를 한 번만 읽고 값을 한 번만 대입하면 되므로 in을 활용한 방법에 비해 효율적이나, get 메소드 활용에 비해 비효율적이다.

  • get 메소드를 사용하는 방법
names = votes.get(key)
if names is None:
	votes[key] = names = []

names.append(who)

추가적으로 BetterWay10. '대입식을 사용해 반복을 피하라' 에서 소개된 왈러스 연산자를 사용하면 더 짧게 쓸 수 있다.

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

names.append(who)

→ 키를 한 번만 읽고 값을 한 번만 대입한다, KeyError 방법에 비해 코드가 짧다. 가장 효율적이고 가독성을 높일 수 있는 방법이다.

setdefault 메서드를 활용한 방법

위 패턴을 더 간단히 사용할 수 있게 해주는 dict 타입의 setdefault 메서드가 있다.

딕셔너리에서 키를 사용해 값을 가져오려고 시도하고, 키가 없으면 제공받은 디폴트 값을 키에 연관시켜 딕셔너리에 대입한 다음 키에 연관된 값을 반환한다.

names = votes.setdefault(key, [])
names.append(who)

→ get과 대입식을 사용하는 것 보다 짧지만 저자는 set이라는 단어가 위 패턴의 동작을 잘 설명하지 못하는 단어라 가독성이 좋지 않다고 판단한다.

  • 문제점
data = {}
key = 'foo'
value = []
data.setdefault(key, value)
print('이전:', data)
value.append('hello')
print('이후:', data)

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

→ 키가 없으면 setdefault에 전달된 디폴트 값이 별도로 복사되지 않고 딕셔너리에 직접 대입된다.

→ 호출할 때마다 리스트를 만들어야 해서 성능 저하의 여지가 있다.

→ setdefault 대신 defaultdict 메서드를 사용할 것을 고려해 보아야 한다.

기억해야 할 내용

  • 딕셔너리 키가 없는 경우를 처리하는 방법에는 in 식을 사용하는 방법, KeyError 예외를 사용하는 방법, get 메서드를 사용하는 방법, setdefault 메서드를 사용하는 방법이 있다.
  • 카운터와 같이 기본적인 타입의 값이 들어가는 딕셔너리를 다룰 때는 get메서드가 가장 좋고, 딕셔너리에 넣을 값을 만드는 비용이 비싸거나 만드는 과정에 예외가 발생할 수 있는 경우에도 get 메서드를 사용하는 편이 낫다.
  • 해결하려는 문제에 dict의 setdefault 메서드를 사용하는 방법이 가장 적합해 보인다면 setdefault 대신 defaultdict를 사용하는 것을 고려해보아야 한다.

0개의 댓글