2011년 11월 19일 토요일

Python Persistence (1) : Pickle 모듈

Python 3.2 Pickle 모듈

오늘 포스트는 인제대학교 의용공학과 전공 과정인 중급실용전산 수업에서 다뤘던 Persistence 관련 모듈 중 Pickle에 관한 것이다. 컴퓨터 프로그래밍에 있어 영속화를 익히는 과정은 대체적으로 ASCII 포맷의 Text 파일 다루기, Binary 포맷의 파일 다루기, 시리얼화(serialization), 마크업(HTML, XML 등의 ASCII 또는 그 외 Binary Markup), 데이터베이스 전체를 다루거나 또는 그 일부만 언급한 책자도 많다. 위 과정에서 채택한 주교재인 Head First Programming 에선 시리얼화를 이용한 영속화 모듈인 Pickle과 Shelve를 다루지 않으므로 이를 보강하는 의도에서 이 포스트와 다음 포스트인 Python Persistence (2) : Shelve 모듈에서 이들에 대해 살펴보도록 한다.

Pickle이나 Shelve와 같은 시리얼화를 이용한 영속화 작업을 수행하는 이유는 프로그램에서 생성한 객체의 실행 시 담고 있는 그 상태 값을 그대로 컴퓨터의 저장 매체에 저장했다가 추후에 그대로 사용하기 위한 목적을 쉽게 달성하기 위해서다. C#이나 Java 등에서도 이러한 시리얼화를 지원하지만 Python은 Pickle 및 Shelve 모듈로 간단히 구현할 수 있도록 했다.

Pickle 모듈을 이용한 코드 예제는 간단한 일정관리용 콘솔 프로그램으로 하도록 한다. 우선 콘솔용 UI를 아래와 같이 작성했고, 그 출력은 다음과 같다.

  1: #!/usr/bin/env python
  2: # -*- coding: UTF-8 -*-
  3: 
  4: def clearAll():
  5:     return []
  6: 
  7: def printList(items):
  8:     print ("-" * 30)
  9:     print ("%d개의 일정이 등록되어 있습니다." % len(items))
 10:     for item in items:
 11:         print("\t%s" % item)
 12:     print ("-" * 30)
 13: 
 14: import pickle
 15: 
 16: toDo = [] # 사용자가 입력한 일정 담을 리스트
 17: menu = ["출력", "전체 제거", "추가"]    # 메뉴 리스트
 18: running = True
 19: 
 20: print ("*" * 30)
 21: print ("일정관리")
 22: print ("*" * 30)
 23: 
 24: while running:
 25:     option = 1
 26: 
 27:     for m in menu:
 28:         print ("%d. %s" % (option, m))
 29:         option += 1
 30:     print ("%d. %s" % (option, "종료"))
 31:     try:
 32:         command = int(input("원하는 메뉴 번호를 입력하세요: "))
 33:     except ValueError:
 34:         command = 0
 35: 
 36:     if command == option:
 37:         running = False
 38:     elif command == 1:
 39:         printList(toDo)
 40:     elif command == 2:
 41:         toDo = clearAll()
 42:     elif command == 3:
 43:         do = input("\t일정명: ")
 44:         toDo.append(do)
 45:         printList(toDo)


figure01



Pickle 모듈을 사용하기 위해서는 14행과 같이 모듈을 import 한다. Pickle에서는 파일을 바이너리 포맷으로 다루게 되므로 파일 읽거나 쓸 때 파일모드는 반드시 binary 포맷으로 해야 한다. 즉 open() 내장 함수의 모드 파라메터를 ‘rb’와 ‘wb’로 해야 한다. 우선 파일을 저장하는 save() 함수를 다음과 같이 작성했다. Pickle 모듈의 파일 저장 함수는 dump() 이다. 오류처리 구문을 제외하면 이 함수는 with 문을 통해 파일 처리를 Python의 Context Manager에 위임하고, 저장할 객체(이 예제에서는 list object)와 파일 핸들러를 pickle.dump() 함수에 파라메터로 넘겨주는 것이 다이다.



  1: def save(items, fname="items.todo"):
  2:     """
  3:     save todo items into binary file
  4:     """
  5:     try:
  6:         with open(fname, "wb") as fh:
  7:             pickle.dump(items, fh)
  8:     except IOError as ioerr:
  9:         print ("File Error: %s" % str(ioerr))
 10:     except pickle.PickleError as pklerr:
 11:         print ("Pickle Error: %s" % str(pklerr))


위 save() 함수를 첫 코드에 작성 후, 첫 코드의 46번째 줄에 save(toDo)를 추가하면 프로그램 실행 시 파일이 생성된다. 여기서 save(toDo) 코드는 while 문이 종료되는 시점에 호출되도록 들여쓰기(indentation)을 주의해야 한다. 코드 일부와 그 실행 시 예는 아래와 같으며, 생성된 파일(items.todo)에 저장된 내용도 그 아래 표시했다.



23: ...
24: while running:
25: ...
45:         printList(toDo)
46: save(toDo)


figure02



figure03



이제 프로그램 실행 시 Pickle 모듈로 저장한 파일이 있다면 이를 읽어 들여 되돌려 주는 load() 함수를 추가한다.  기존에 생성된 파일이 없으면 save() 함수에 빈 리스트 하나를 파라메터로 넘겨 비어있는 파일 하나를 만들도록 했다. 그 코드는 아래와 같다. 오류처리 구문을 제외하면, save() 함수와 마찬가지로 파일 핸들러를 얻고 이를 pickle.load() 함수에 파라메터로 넘겨 파일을 읽어 들이면 된다.



  1: def load(fname="items.todo"):
  2:     """
  3:     load todo items binary file into memory
  4:     and assigns its values to corresponding object's states
  5:     """
  6:     savedItems = [] # 빈 리스트 생성
  7:     try:
  8:         with open(fname, "rb") as fh:
  9:             savedItems = pickle.load(fh)
 10:     except IOError as ioerr:
 11:         save(savedItems, fname) # create empty file
 12:     except pickle.PickleError as pklerr:
 13:         print ("Pickle Error: %s" % str(pklerr))
 14:     finally:
 15:         return savedItems


이 프로그램의 메인 부분에 해당하는 첫 코드의 16행 toDo = [] 코드를 아래와 같이 수정 후 프로그램을 실행 후, 출력 메뉴를 선택하면 그 아래 그림에 나타낸 것과 같이 앞서 save() 함수 적용 후 입력한 내용이 출력되는 것을 확인 할 수 있다.



...
16: toDo = load() # 사용자가 입력한 일정 담을 리스트
...


figure04



이 포스트에서는 Python에서 Pickle 모듈을 이용한 영속화 예제를 살펴봤다. 이 예제의 경우 단일한 일정목록을 그 개수에 제한 없이 사용할 수 있지만 날짜와 같은 구분 지을 수 있는 키로 여러 데이터 세트를 상황에 맞게 사용하려면 리스트 대신 딕셔너리 객체를 이용하면 되지만 시리얼화로 저장한 파일 내용 전체를 읽어 들여야 해서 비효율 적이다. 필요한 부분만 별도로 관리할 수 있는 모듈이 포스트 처음에 언급한 Shelve 모듈이다. 이는 다음 포스트에서 살펴보도록 한다. 이 프로그램의 전체 코드는 아래와 같다.



  1: #!/usr/bin/env python
  2: # -*- coding: UTF-8 -*-
  3: #-------------------------------------------------------------------------------
  4: # Name:        TodoListPickle
  5: # Purpose:      Pickle 모듈을 이용한 파일 영속화(persistence) 예제
  6: #
  7: # Author:      oscarpark
  8: #
  9: # Created:     18-11-2011
 10: # Copyright:   (c) oscarpark 2011
 11: # Licence:     Licence Free
 12: #-------------------------------------------------------------------------------
 13: 
 14: 
 15: def clearAll():
 16:     return []
 17: 
 18: def printList(items):
 19:     print ("-" * 30)
 20:     print ("%d개의 일정이 등록되어 있습니다." % len(items))
 21:     for item in items:
 22:         print("\t%s" % item)
 23:     print ("-" * 30)
 24: 
 25: def save(items, fname="items.todo"):
 26:     """
 27:     save todo items into binary file
 28:     """
 29:     try:
 30:         with open(fname, "wb") as fh:
 31:             pickle.dump(items, fh)
 32:     except IOError as ioerr:
 33:         print ("File Error: %s" % str(ioerr))
 34:     except pickle.PickleError as pklerr:
 35:         print ("Pickle Error: %s" % str(pklerr))
 36: 
 37: def load(fname="items.todo"):
 38:     """
 39:     load todo items binary file into memory
 40:     and assigns its values to corresponding object's states
 41:     """
 42:     savedItems = [] # 빈 리스트 생성
 43:     try:
 44:         with open(fname, "rb") as fh:
 45:             savedItems = pickle.load(fh)
 46:     except IOError as ioerr:
 47:         save(savedItems, fname) # create empty file
 48:     except pickle.PickleError as pklerr:
 49:         print ("Pickle Error: %s" % str(pklerr))
 50:     finally:
 51:         return savedItems
 52: 
 53: import pickle
 54: 
 55: toDo = load() # 사용자가 입력한 일정 담을 리스트
 56: menu = ["출력", "전체 제거", "추가"]    # 메뉴 리스트
 57: running = True
 58: 
 59: print ("*" * 30)
 60: print ("일정관리")
 61: print ("*" * 30)
 62: 
 63: while running:
 64:     option = 1
 65: 
 66:     for m in menu:
 67:         print ("%d. %s" % (option, m))
 68:         option += 1
 69:     print ("%d. %s" % (option, "종료"))
 70:     try:
 71:         command = int(input("원하는 메뉴 번호를 입력하세요: "))
 72:     except ValueError:
 73:         command = 0
 74: 
 75:     if command == option:
 76:         running = False
 77:     elif command == 1:
 78:         printList(toDo)
 79:     elif command == 2:
 80:         toDo = clearAll()
 81:         save(toDo)
 82:     elif command == 3:
 83:         do = input("\t일정명: ")
 84:         toDo.append(do)
 85:         printList(toDo)
 86: save(toDo)



댓글 없음:

댓글 쓰기