ChangHyeon Nam's Blog notes and thoughts

딕셔너리 키가 없는 경우 get을 사용하라

Comments

[Effective Python] Better way 16: in을 사용하고 딕셔너리 키가 없을 때 KeyError를 처리하기 보다는 get을 사용하라

  • 딕셔너리 키가 없는 경우를 처리하는 방법으로는 In식을 사용하는 방법, KeyError 예외를 처리하는 방법, get 메서드를 사용하는 방법, setdefault 메서드를 사용하는 방법이 있다.
  • 기본적인 타입의 값이 들어가는 딕셔너리나 리스트와 같은 비용이 비싼 자료구조를 딕셔너리를 만들때 get 메서드를 사용하는 것이 가장 낫다.

이 chapter에서는 딕셔너리의 setdefault 메서드의 예외 및 비용과 관련된 내용을 다룹니다. 다음 코드를 보면 get과 setdefault의 동작이 다른것을 볼 수 있습니다.

>>> my_dict = {}
>>> my_dict.setdefault('some key', 'a value')
'a value'
>>> my_dict
{'some key': 'a value'}
>>> my_dict.get('some key2', 'a value2')
'a value2'
>>> my_dict
{'some key': 'a value'}

setdefault의 경우 key가 존재하지 않을 경우 default값을 갖고있는 새로운 키를 딕셔너리에 대입합니다. get의 경우 default값이 반환됩니다.


  • 딕셔너리와 상호작용 하는 세가지 기본연산은 키나 키에 연관된 값에 접근,삭제,대입하는 것입니다.

      counters ={
          '소보로':1,
          '단팥빵':2,
      }
      key ='꽈베기'
    
      if key in counters:
          count = counters[key]
      else:
          count=0
      counters[key]=count+1
    

    위의 코드처럼 in 연산자를 사용하여 key의 존재 유무를 확인할 수 있습니다.

      try:
          count = counters[key]
      except KeyError:
          count =0
      counters[key]=count+1
    

    다른 방법으로는 KeyErorr를 활용하는 방법도 있습니다. 하지만 위의 두 방법은 더 길고, 가독성이 좋지 못합니다.

      count = counters.get(key,0)
      counters[key]= count+1
    

    get 메서드를 사용하면 짧고, 간결하게 나타낼 수 있습니다.

  • 딕셔너리에 저장된 값이 리스트와 같은 복잡한 값일 경우에 대해 살펴봅시다.

      votes = {
          '소보로':['철수','민수'],
          '단팥빵':['미애','미정'],
      }
      key = '꽈베기'
      who = '종원'
      if key in votes:
          names = votes[key]
      else:
          votes[key]=names=[]
      names.append(who)
      print(votes)
    
      try:
          names = votes[key]
      except KeyError:
          votes[key]=names=[]
      names.append(who)
      print(votes)
    

    같은 방식으로 in 또는 KeyError를 활용하여 위와 같이 정리할 수 있습니다.

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

    이 경우에도 get 메서드를 활용하는 방법이 더 가독성이 좋습니다. warlus연산을 사용하면 코드를 더 짧게 작성할 수 있습니다.

  • setdefault 메서드를 사용하여 같은 동작을 구현할 수 있습니다.

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

    하지만 메서드 이름이 setdefault이고, 이는 메서드의 동작을 분명하게 나타내지 못합니다. (get_or_set이 아닌.) 또한 setdefault에 전달된 defualt값이 별도로 복사되지 않고 딕셔너리에 직접적으로 대입됩니다. 그래서 다음과 같은 오류를 발생시킬 수 있습니다.

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

    키에 해당하는 디폴트값을 setdefault에 전달할 때마다 그 값을 새로 만들어야 한다는 뜻이고, 호출 할때마다 리스트를 만들어야 하므로 성능이 크게 저항 될 수 있습니다.

      data = {}
      value = []
      for i  in range(3):
          key = i
          data.setdefault(key, value)
          value.append('hello')
          print(data)
      # {0: ['hello']}
      # {0: ['hello', 'hello'], 1: ['hello', 'hello']}
      # {0: ['hello', 'hello', 'hello'], 1: ['hello', 'hello', 'hello'], 2: ['hello', 'hello', 'hello']}
    

    위와 같이 리스트를 매번 새로 만들어 성능이 저하 되는 동시에 unexpected 동작을 할 수 있습니다.

    다음 챕터에선 같은 상황에서 setdeafualt보다 나은 선택지인 defaultdict에 대해 알아봅시다.