파이썬 딕셔너리 입력 순서

파이썬 OrderedDict

애초에 이번 포스팅은 파이썬 딕셔너리 자료형에 데이터 입력 시 키 값 순서가 무작위로 생성되는 현상을 바로 잡을 수 있는 collections패키지의 OrderedDict를 활용하는 팁을 준비하려 했습니다.

 

딕셔너리 자료형의 특성을 고려한다면 키 값 입력 순서는 사실 크게 중요하지는 않습니다. 하지만 로직에 따라 이 순서 또한 중요한 역할을 할 때가 있습니다. 예를 들어 모듈을 실행하는데 딕셔너리 자료형에 설정값들이 세팅되어 있고 이 값들을 차례대로 적용해야 하는 경우가 발생하였을 때 입력 순서는 중요하게 작용할 것입니다. 이런 경우가 발생할까?라고 생각하실지도 모르겠지만 제가 실제로 이런 요구 사항이 있는 모듈을 작성했었습니다.

 

물론 순서를 보장할 수 없음을 먼저 인지하고 이에 맞는 로직을 구현할 수도 있겠습니다. 하지만 이 순서가 보장된다면 아마도 그 기능을 구현하는 데 있어서 타이핑 수가 많이 줄어들 것입니다. 그리고 개발 당시에는 당연히 순서가 보장될 것이라 생각했습니다.

 

고심을 하여 로직을 최적화시킨 모듈은 수천 라인에 육박하였고 최종 테스트 진행 시 딕셔너리 자료형의 입력 순서가 보장되지 않는 것을 발견했을 때 저에게 두 가지 선택 사항만이 있었습니다. 순서를 보장할 수 있도록 보완 로직을 구현할 것인지 아니면 파이썬 딕셔너리 자료형 입력 순서를 보장하도록 수정할 것인지 갈림길에 서 있었습니다.

 

잠깐 고민을 하다가 정보의 바다 인터넷 검색을 시작하였습니다. 인터넷 검색 시 "입력 순서" 혹은 "딕셔너리 키 순서" 등과 같이 "순서"라는 키워드가 들어가다 보니 검색 결과는 거의 대부분 키 값을 가져와 sort 하는 게시물이 태반이었습니다. 찾고자 하는 정보는 찾을 수 없어 정보 검색을 중단하려던 찰나 파이썬 매뉴얼을 찾아보기 시작했고 매뉴얼에서 collections 패키지를 발견하고 유래카를 외쳤습니다.

 

혹시 낮은 버전의 파이썬을 사용하시는 분이 이와 같은 상황에 직면했을 때 조금의 도움이 될 수 있도록 해결했던 방법을 알려 드리겠습니다.

 

import collections

dict_var = collections.OrderedDict()

 

dict_var 변수처럼 딕셔너리 선언을 하고 이후 딕셔너리 사용 문법으로 dict_var변수를 다루면 입력한 순서대로 키 값이 저장되는 것을 확인할 수 있습니다.

만약 제가 저 두 줄의 코드를 찾지 못했었다면 딕셔너리 키 값 순서를 보장하는 보완 로직 수십 줄이 추가되었을 것입니다.

 

그리고 collections패키지에 대해 포스팅을 하고자 파이썬 3.7.4 버전이 과연 많은 입력에도 그 순서를 보장할 수 있을까?라는 생각에 간단히 코드를 작성해 보려고 합니다.

 

확인해 보고자 하는 시나리오 및 조건은 다음과 같습니다.

 

1. 딕셔너리 자료형에 입력할 데이터 건수를 파라미터로 입력 받음 (최대 : 22369620)

2. 입력받은 수만큼 sequence 리스트 값 생성 후 순서 섞음

3. 리스트 값을 섞은 차례대로 딕셔너리 자료형 변수의 키로 입력하고 값은 1부터 시작하여 차례대로 증가

4. 딕셔너리 변수의 키를 차례대로 꺼내어 값으로 입력한 순번대로 데이터가 추출되는지 확인

5. 만약 순서가 다를 경우 해당 넘버를 포함하는 메시지 출력

6. 순서가 모두 동일하다면 순서가 동일하다는 메시지 출력

7. 실행 시간 체크 

 

 

 

대략 생각한 조건은 위와 같습니다. 이를 토대로 손 가는 대로 작성한 코드는 다음과 같습니다.

 

import random
import sys
import time

if len(sys.argv) == 1:
    print("""\
사용법:
    dic_ordered_test 카운터숫자

파라미터 정보:
    카운터숫자 = 딕셔너리 테스트 입력 건수 (최대 : 22369620)\
    """)
    exit(1)
else:
    if not sys.argv[1].isdecimal():
        print("숫자만 입력...")
        exit(1)

start_time = time.time()

if int(sys.argv[1]) > 22369620:
    counter = 22369620
else:
    counter = int(sys.argv[1])

make_list = list(filter(lambda x:x, range(1, counter + 1)))
random.shuffle(make_list)

test_dict={}
make_seq = 0

for get_num in make_list:
    make_seq += 1
    test_dict[get_num] = make_seq

check_seq = 0
diff_cnt = 0

for get_key in test_dict.keys():
    check_seq += 1

    if check_seq != test_dict[get_key]:
        print("순서 다름 : (check_seq = %s | dict_seq = %s"%(str(check_seq), str(test_dict[get_key])))
        diff_cnt += 1

if diff_cnt == 0:
    print("순서 안바뀜")
else:
    print("%s 건 불일치"%(str(diff_cnt)))

print(time.time() - start_time)

exit(0)

 

위의 코드를 실행하면서 건수를 바꿔가며 3번씩 실행한 결과 값은 다음과 같습니다.

 

건수 1 회차 2 회차 3 회차 불일치 여부
100 0.00232 0.00103 0.00077 일치
10,000 0.01496 0.02645 0.01994 일치
100,000 0.14780 0.16451 0.14763 일치
500,000 0.86488 0.90262 0.83868 일치
1,000,000 1.79173 1.76421 1.90603 일치
5,000,000 9.79379 9.87681 9.88871 일치
10,000,000 20.02651 19.95817 20.53900 일치
20,000,000 40.54734 41.34587 40.16532 일치
22,369,620 45.77274 45.43898 45.95913 일치

 

우려했던 상황과는 다르게 모두 일치하는 결과 값을 확인할 수 있었습니다. 데이터 건수는 백만 건 이후부터 조금 버거워 보이기 시작합니다. 수행 시간이야 수행환경에 따라 다르고 작성한 로직에 따라 달라지는 사항으로 큰 의미는 없겠지만 그냥 참고 사항으로 같이 작성해 보았습니다.

 

마치며...

이 코드를 작성하면서 입력 건수는 제약 없이 하려고 하였습니다. 하지만 테스트 과정에서 22,369,620건이 넘으면 메모리 에러가 발생하는 것을 확인하였습니다. 처음에는 lambda 라인에서 에러가 발생하여 이 부분을 단순 for문으로 변경하여 재차 확인 결과 이번에는 딕셔너리 건수 입력 시 동일하게 메모리 에러가 발생하였습니다.

 

입력한 데이터 크기와 연관이 있을까?라는 생각에 키 값에 입력하는 데이터 크기를 충분히 크게 하여 입력했지만 역시나 22,369,620건을 초과하게 되면 메모리 오류가 발생하였습니다.

 

이를 바탕으로 유추해 보자면 딕셔너리 변수에 입력하는 키에 대응하는 값의 크기는 상관이 없지만 할당하는 데이터 건수에는 제약이 있어 보입니다. 테스트를 진행한 파이썬은 32비트로 만약 64비트를 설치했다면 건수에 변화가 있었으리라 판단됩니다. 64비트였다면 테스트를 진행한 노트북의 16GB 메모리를 모두 사용했을 테니 말이죠.

 

만약 여러분이 32비트 파이썬을 사용하신다면 딕셔너리 자료형 변수에 22,369,620건을 초과하는 로직을 구현하신다면 유의하셔야 할 사항입니다. 물론 장비에 따라 다른 프로세스 들의 메모리 사용량에 따라 다른 결과가 나올 수 있습니다.

 

MORE