ㅍㅍㅋㄷ

python 3와 python 2의 dictionary 차이 - view object 본문

Programming/Python

python 3와 python 2의 dictionary 차이 - view object

클쏭 2019. 2. 19. 17:17


python 3와 python 2의 dictionary 차이 - view object 


python 3로 가면서 python 2에 비해 여러가지가 변경되었다. 

그 중에 python 3에서 새롭게 생긴 dictionary의 view object 에 대해서 알아 보려 한다.



Dictionary 메서드 - items() / keys() / values()


python에서 dictionary 를 탐색할때 가장 많이 하는 행위 중 하나가 바로 for문을 통해 key와 value 값을 가져오는 것일 것이다.


dic = {'a': 100, 'b': 50, 'c': 10, 'd': 500}


for k,v in dic.items():

   print("key=%s, value=%s" %(k,v))


>>> key=a, value=100

>>> key=d, value=500

>>> key=c, value=10

>>> key=b, value=50


items() 는 dictionary 의 key와 value 값을 가져오는 메서드이다. 

items() 메서드 외에도 key 값만 가져오는 keys() 와 value 값을 가져오는 values() 메서드도 있다.


그런데 dictionary 에서 제공되는 메서드를 사용할 때 python 3는 기존의 python 2와 약간의 차이를 보인다. 

아래 python 3 일때 items, keys, values 메서드를 사용할때 반환되는 결과를 보자.


dic = {'a': 100, 'b': 50, 'c': 10, 'd': 500}


dic.items()

>>> dict_items([('c', 10), ('a', 100), ('b', 50), ('d', 500)])


dic.keys()

>>> dict_keys(['c', 'a', 'b', 'd'])


dic.values()

>>> dict_values([10, 100, 50, 500])



 Python 2 일때는 아래와 같다.


dic = {'a': 100, 'b': 50, 'c': 10, 'd': 500}


dic.items()

>>> [('a', 100), ('c', 10), ('b', 50), ('d', 500)]


dic.keys()

>>> ['a', 'c', 'b', 'd']


dic.values()

>>> [100, 10, 50, 500]



언뜻 보면 큰 차이 없어보이지만, 결과값의 type을 확인해 보면 실체가 완전히 다른것을 알 수 있다.


# Python 3

type(dic.values())

>>> <class 'dict_values'>



# Python 2

type(dic.values())

>>> <type 'list'>


items() 나 keys() 의 type을 보면 아래와 같이 dict_items, dict_keys 로 확인 된다.

물론 python2 에서는 메서드에 상관없이 모두 list type으로 반환됨을 확인할 수 있다.


type(dic.items())

>>> <class 'dict_items'>


 type(dic.keys())

>>> <class 'dict_keys'>



간단히 생각했을때, python 2에서 처럼 list 로 반환해 주면 훨씬 사용하기 심플할 것이다. 

근데 python 3에서는 왜 이런 추가적인 class를 만든 것일까.




Dictionary view objects


python docs 에서는 이것을 view object 라고 설명한다.

아래 자세한 내용을 확인해 보자.


The objects returned by dict.keys(), dict.values() and dict.items() are view objects. They provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes, the view reflects these changes.


Dictionary views can be iterated over to yield their respective data, and support membership tests:


view object를 만든 가장 큰 이유는 dictionary의 동적 변경을 반영해 처리하기 위해서라고 설명한다. 

이것이 무엇을 의미하는 것인지 알아볼 필요가 있다.



먼저 python 2에서 아래 상황을 살펴보자.


dic = {'a': 100, 'b': 50, 'c': 10, 'd': 500}

dic_values = dic.values()


dic_values

>>> [100, 10, 50, 500]    # List type


먼저 python 2에서는 values() 메서드를 사용해 dictionary 의 value 값만 취하게 되면 위와 같이 list type 으로 값을 반환하게 된다.

이때, 본래 dictionary 의 값, 해당 예제에서는 dic 변수 값 중 하나를 변경해보자.


dic['a'] = 1


dic

>>> {'a': 1, 'c': 10, 'b': 50, 'd': 500}


 key가 'a' 인 값을 1로 변경하였다. 

 하지만, 이전에 values() 메서드를 통해 value 값만 가져와 저장했던 dic_values 값은 어떨까.


dic_values

>>> [100, 10, 50, 500]


기존 dic 변수에서 변경된 "a = 1" 의 값이 dic_values 에는 반영되지 않았음을 확인 할 수 있다.

즉, Python 2에서 values() 메서드는 해당 dictionary 에 있던 값의 내용을 가져와 list로 return 하게 되는데, 이때 본래의 dictionary 값과는 다른 메모리에 저장하게 된다. 

 
그런데 뭔가 이상하다.

우리가 알고 있는 Python의 dictionary는 mutable 한 특성을 가지고 있다. 
즉, dictionary 의 value 값은 call by reference 와 같은 방식으로, dictionary 값이 변경되면 그 변경 값이 참조되어 함께 변경되는 특성을 가지고 있다.

dic = {'a': 100, 'd': 500, 'c': 10, 'b': 50}

dic_copy = dic

dic_copy['b'] = 50000

dic
>>> {'a': 100, 'd': 500, 'c': 10, 'b': 50000}

id(dic)
>>> 140169620023944

id(dic_copy)
>>> 140169620023944


위와 같은 간단한 예제를 보면 알수 있듯이,
dictionary 값은 같은 주소를 참조하기 때문에 값이 변경되면 모두 변경됨을 알 수 있다.

그런데, values() 메서드를 통해 dictionary에 value 값을 가지고 오게 되면 이러한 특성과는 다르게 변경값이 참조되지 않고 별도로 값을 취한다는 것이다. dictionary의 mutable 한 특성과 다른 것으로 자칫 사용하는데 혼란을 줄 수 있는 부분이다.

따라서, python 3 에서는 이러한 dictionary의 mutable 한 특성을 그대로 따르기 위해 view object 라는 것을 만들어 해결 한 것으로 보인다. 

아래는 python 3에서 values() 를 값을 가져와 비교한 것이다.

dic = {'a': 100, 'b': 50, 'c': 10, 'd': 500}

dic_values = dic.values()


dic_values

>>> dict_values([10, 100, 50, 500])


dic['a'] = 1


dic_values

>>> dict_values([10, 1, 50, 500])


python 3에서는 python 2와는 다르게 변경된 dictionary 값이 dict_values 에도 참조되어 변경됨을 볼 수 있다.
기존 우리가 알고 있는 dictionary 의 mutable한 특성과 동일하게 동작함을 볼 수 있다.


그리고 또 하나의 차이점은 바로 메모리의 효율성 이다.
python 3의 view object 는 list 가 아닌 iterable 한 object 이며, 이는 generator 가 가지고 있는 특징과 동일한 효과를 낸다. 
generator 에 대한 자세한 설명은 다음 포스팅을 참고한다. (python generator란 무엇인가)

아래 예제를 보자. 
Python 3에서 items() 메서드를 사용하여 dictionary의 item을 가져왔을때 메모리 사용 현황을 보자.

# Python 3
import sys
dic = {'a': 100, 'b': 50, 'c': 10, 'd': 500}
dic_items = dic.items()

sys.getsizeof(dic_items)
>>> 48


dic2 = {'a': 100, 'd': 500, 'c': 10, 'b': 50, 'e': 30, 'f': 1, 'g': 15, 'h': 40, 'i': 50}
dic2_items = dic2.items()

sys.getsizeof(dic2_items)
>>> 48

dictionary 의 크기에 상관 없이 items() 메서드를 통해 가져온 값은 동일한 메모리 사이즈를 보인다.


python2에서는 어떤지 확인해 보자.


# Python 2

import sys
dic = {'a': 100, 'b': 50, 'c': 10, 'd': 500}
dic_items = dic.items()

sys.getsizeof(dic_items)
>>> 104


dic2 = {'a': 100, 'd': 500, 'c': 10, 'b': 50, 'e': 30, 'f': 1, 'g': 15, 'h': 40, 'i': 50}
dic2_items = dic2.items()

sys.getsizeof(dic2_items)

>>> 144


python 2에서는 dictionary 크기에 따라 items() 메서드로 가져온 값의 메모리 사이즈가 다르다. 결과값을 list 로 가져오기 때문이다.


사실, python 2에서도 python 3의 items() 와 동일하게 iterable 한 object로 가져오는 방법을 제공해 준다. 바로 iteritems() 이다.


# Python 2

dic.items()

>>> [('a', 100), ('c', 10), ('b', 50), ('d', 500)]  



dic.iteritems()

>>> <dictionary-itemiterator object at 0x7f49c663cc00>


python 3 에서는 python 2에서 제공되던 items() 와 iteritems() 를 items() 메서드 하나로 통합했다고 보면 될 것 같다.




[참고]

  • https://docs.python.org/3.3/library/stdtypes.html#dict-views
  • https://docs.python.org/3/library/stdtypes.html#mapping-types-dict


Comments