CodeOnWeb
로그인

챕터 13. 파이썬 객체 직렬화

파이썬 객체를 직렬화 하는 방법을 알아봅니다.

Park Jonghyun 2015/09/01, 23:35

내용

파이썬 3에 뛰어들기 (22)

챕터 -1. “파이썬 3로 뛰어들기”에서 달라진 점 챕터 0. 파이썬 설치하기 챕터 1. 첫 파이썬 프로그램 챕터 2. 고유 자료형 챕터 3. 컴프리헨션(Comprehensions) 챕터 4. 문자열(Strings) 챕터 5. 정규표현식(Regular Expressions) 챕터 6. 클로저와 제너레이터(Closures & Generators) 챕터 7. 클래스와 반복자(Classes & Iterators) 챕터 8. 고급 반복자(Advanced Iterators) 챕터 9. 단위 테스트(Unit Testing) 챕터 10. 리팩토링(Refactoring) 챕터 11. 파일 챕터 12. XML 챕터 13. 파이썬 객체 직렬화 챕터 14. HTTP 웹서비스 챕터 15. 사례 연구: chardet을 파이썬 3로 이식하기 챕터 16. 파이썬 라이브러리 패키징하기 부록 A. 2to3를 이용해서 코드를 파이썬 3로 이식하기 부록 B. 특수 함수 이름 부록 C. 이제 어디로 가야 할까요? 부록 D. 문제 해결

"이 아파트에 산 뒤로 매주 토요일이 되면, 나는 6:15에 깨어나서 2% 우유 1/4 컵에 시리얼 추가해서 내 입에 쏟아 넣고, 이 소파의 이쪽 끝에 앉아 BBC America를 틀어 닥터 후를 보지."
쉘던(Sheldon), 빅뱅이론(The Bag Bang Theory)

뛰어들기

수박 겉핥기 식으로만 보자면 직렬화(serialization)의 개념은 간단합니다. 메모리에 어떤 데이터 구조가 있는데 이를 저장해서 다시 사용하거나 누군가에게 전송하고 싶다고 해봅시다. 어떻게 해야 할까요? 어떤 방식으로 저장할건지, 어떻게 재사용 할건지, 또 누구에게 보낼건지에 따라 다를 수 있겠죠. 게임을 종료할 때 진행 상태를 저장해 두었다가 다시 실행하면 이어했던 경험이 있을겁니다. (사실, 게임이 아닌 수많은 다른 애플리케이션에서도 이런 동작을 합니다.) 이 경우, 여러분의 "진행 상황"을 담고 있는 데이터 구조는 종료할 때 디스크에 저장되었다가, 다시 실행할 때 불러들여야 됩니다. 이런 데이터는 한 프로그램 내에서만 사용될 목적으로 생성되었으므로 다른 프로그램에서 읽어들일 수 없습니다. 따라서 데이터의 호환성 문제는 프로그램의 버전이 올라갔을 때 이전 프로그램에서 저장한 데이터를 읽어들일 수 있는지의 문제 정도로 제한되어 있습니다.

이런 경우 pickle 모듈을 사용하는 것이 좋습니다. 파이썬 표준 라이브러리에 포함되어 있어서 언제나 사용할 수 있습니다. 대부분이 C로 작성되어 빠르기까지 합니다. 복잡한 임의의 파이썬 데이터 구조를 저장할 수 있습니다.

pickle 모듈은 무엇을 저장할 수 있을까요?

  • 파이썬이 제공하는 모든 고유 자료형을 저장할 수 있습니다. boolean, 정수, 실수, 복소수, 문자열, 바이트 객체, 바이트 배열, None을 포함한 모두를요.
  • 고유 자료형을 조합한 리스트, 튜플, 사전, 집합을 저장할 수 있습니다.
  • 리스트, 튜플, 사전, 집합을 항목으로 가지는 리스트, 튜플, 사전, 집합을 저장할 수 있습니다. (파이썬이 지원하는 최대 깊이까지 계속 가능합니다.)
  • 함수, 클래스, 클래스의 개체도 저장할 수 있습니다.

이것만으로는 충분하지 않으면 pickle 모듈을 좀 확장할 수도 있습니다. 관심이 있으시다면 이 챕터 끝부분의 [더 읽을거리]를 참고하세요.

이 챕터에서 사용할 예제에 대한 간단한 주의사항

이 챕터는 두 개의 파이썬 쉘을 가지고 이야기를 진행합니다. pickle과 json 모듈을 설명하면서 두 개의 파이썬 쉘 사이를 왔다 갔다 하라는 말씀을 많이 드릴거에요.

일을 좀 더 분명하고 쉽게 하기 위해 파이썬 쉘을 열고 다음 변수를 정의해두세요.

기존 창은 그대로 두고 새로운 파이썬 쉘을 열고 다음 변수를 정의하세요.

이 두 변수를 이용해 각 예제에서 어떤 쉘을 사용하고 있는지 알려드릴겁니다.

Notice

이 챕터의 내용은 파이썬 쉘을 필요로 하므로 CodeOnWeb에서는 올바른 코드 실행 결과를 얻을 수 없습니다.

Pickle 파일에 데이터 저장하기

pickle 모듈을 사용하려면 데이터 구조가 있어야 합니다. 하나 만들어 볼까요.

  1. 파이썬 쉘 #1에서 입력합니다.
  2. 유용한 정보를 담고 있는 파이썬 사전을 하나 만들려고 합니다. 아톰 피드의 entry가 적당할 것 같네요. pickle 모듈을 이용해서 다양한 일을 해보기 위해 다른 형식의 데이터도 제 마음대로 추가했습니다. 구체적인 값은 중요하지 않으니 너무 신경쓰지 마시고요.
  3. time 모듈은 시간을 표현(1 ms 시간까지 표현할 수 있습니다)하기 위한 데이터 구조(struct_time)와 이 데이터 구조를 다루기 위한 여러 함수를 가지고 있습니다. str_time 함수는 특정한 형식으로 쓰여진 문자열을 인자로 받은 뒤 그 문자열이 지시하는 시간을 struct_time으로 변환합니다. 여기 있는 문자열을 시간을 지정하는 기본 형식을 따르고 있는데, 형식 코드를 이용해서 여러분이 원하는 형식으로 바꿀 수도 있습니다. 자세한 사항은 time 모듈을 참고해주세요.

아주 멋있어 보이는 파이썬 사전이네요. 파일로 저장해볼까요.

  1. 여전히 파이썬 쉘 #1에 있습니다.
  2. 파일을 열기 위해 open() 함수를 사용했습니다. 파일을 여는 모드는 'wb'인데, 이진 데이터를 쓰는 모드라는 의미입니다. 작업이 끝나면 자동으로 파일을 닫아주기 위해 with 문도 사용했습니다.
  3. pickle 모듈의 dump() 함수는 직렬화 가능한 파이썬 데이터 구조와 열린 파일을 인자로 받은 뒤, 최신 pickle 프로토콜에 지정된 방식에 따라 이진 형식으로 데이터를 직렬화 해서 파일에 저장합니다.

마지막 문장은 매우 중요합니다.

  • pickle 모듈은 파이썬 데이터 구조를 받아서 파일에 저장합니다.
  • 이를 위해 pickle 모듈은 "pickle 프로토콜"이라 불리는 데이터 형식을 이용해서 주어진 데이터 구조를 직렬화합니다.
  • Pickle 프로토콜은 파이썬에서만 사용할 수 있고, 다른 언어에서 사용할 수 있다는 보장은 없습니다. 여러분이 만든 entry.pickle 파일을 이용해서 Perl, PHP, 자바와 같은 다른 언어에서 유용한 일을 하기는 어려울 겁니다.
  • Pickle 모듈이 모든 파이썬 데이터 구조를 다 직렬화할 수 있는 것은 아닙니다. 파이썬에 새로운 형식의 데이터가 추가될 때마다 pickle 프로토콜도 변화되어 왔지만, 여전히 한계는 있습니다.
  • 프로토콜이 변하면서 서로 다른 파이썬 버전 간의 호환성에도 문제가 있을 수 있습니다. 새로운 버전의 파이썬은 예전 방식의 직렬화를 지원하겠지간, 오래된 버전의 파이썬은 새로운 직렬화 형식을 지원하지 못합니다(새로운 데이터 형식을 지원하지 않으니까요).
  • 여러분이 따로 지정해주지 않는다면, pickle 모듈의 함수는 최신 pickle 프로토콜을 사용할겁니다. 따라서 최대한 다양한 형식의 파이썬 데이터 구조를 손쉽게 직렬화 할 수 있습니다. 하지만 이 결과로 만들어진 파일을 최신 pickle 프로토콜을 사용하지 않는 이전 버전의 파이썬에서 읽어들이려고 한다면 문제가 발생할 수도 있습니다.
  • 최신 pickle 프로토콜은 이진 형식을 사용합니다. pickle 모듈에 사용할 파일은 반드시 이진 모드로 여세요. 아니면 쓰는 과정에서 데이터가 망가질겁니다.

Pickle 파일에서 데이터 불러오기

이제 두 번째 파이썬 쉘로 이동합시다. 여러분이 entry 사전을 생성했던 쉘 말고 다른 쉘이요.

  1. 파이썬 쉘 #2에 있습니다.
  2. 여기는 entry라는 변수가 없습니다. 파이썬 쉘 #1에 정의하긴 했지만, 두 쉘은 완전히 독립된 다른 환경에서 돌아갑니다.
  3. 파이썬 쉘 #1에서 만든 entry.pickle 파일을 엽니다. pickle 모듈은 이진 데이터 형식을 사용하므로, pickle 파일은 언제나 이진 모드로 열어야 한다는 것을 잊지 마세요.
  4. pickle.load() 함수는 스트림 객체를 인자로 받아 그 스트림으로부터 직렬화 된 데이터를 읽어들이고, 그 데이터를 다시 원래 형태의 파이썬 객체로 복원한 뒤 반환합니다.
  5. 이제 entry 변수에 익숙해 보이는 키와 값이 들어있네요.

pickle.dump()와 pickle.load()를 사용해서 원래 데이터 구조와 동일한 새로운 데이터 구조를 만들었습니다.

  1. 파이썬 쉘 #1로 돌아갑시다.
  2. entry.pickle 파일을 엽니다.
  3. 직렬화 된 데이터를 로드해서 새 변수인 entry2에 저장합니다.
  4. entry와 entry2 두 변수가 동일하다는 것을 확인할 수 있습니다. 이 쉘에서 여러분은 처음부터 일일이 키-값 변수를 대입해가며 entry를 만들었습니다. 그리고 이 사전을 직렬화해서 entry.pickle 파일에 저장했지요. 이제 그 파일로부터 직렬화 된 데이터를 읽어들여서 원본과 완전히 똑같은 데이터 구조를 복제했습니다.
  5. 똑같은 데이터 구조를 가졌다고 해서 entry, entry2 두 변수 자체가 같은 것은 아닙니다. 제가 "복제"했다고 말씀드렸지요. entry와 entry2는 메모리 상에 서로 다른 주소를 가지는 별개의 변수입니다.
  6. 이 챕터 뒤에서 왜 그런지 알 수 있겠지만, 'tags' 키의 값은 튜플이고 'internal_id' 키의 값은 바이트 객체라는 점을 일단 말해두고 싶습니다.

파일 없이 pickle 이용하기

이전 예제를 통해 파이썬 객체를 디스크 상의 파일에 바로 직렬화해서 쓰는 방법을 알아보았습니다. 하지만 파일이 필요없는 상황에서는 어떻게 해야할까요? 사실 메모리 상의 바이트 객체로 직렬화할 수도 있습니다.

  1. pickle.dumps() 함수는(함수 이름 마지막에 's'를 조심하세요) pickle.dump() 함수와 같은 직렬화 작업을 수행합니다. 다만, 스트림 객체를 받아서 파일에 데이터를 쓰는 대신, 그냥 직렬화된 데이터를 반환한다는 점에서 차이가 있습니다.
  2. Pickle 프로토콜은 이진 데이터 형식을 사용하므로 pickle.dumps() 함수는 바이트 객체를 반환합니다.
  3. pickle.loads() 함수는(이번에도 마지막 's'에 조심하세요) pickle.load() 함수와 같은 역직렬화(deserialisation) 작업을 수행합니다. 다만, 스트림 객체를 받아서 파일로부터 데이터를 읽어들이는 대신, 직렬화된 데이터를 담고 있는 바이트 객체를 받아서 작업한다는 차이가 있습니다.
  4. 최종 결과는 같습니다. 원본 사전의 완벽한 복제품이 만들어졌네요.

바이트와 문자열이 다시 골치아프게 합니다

Pickle 프로토콜은 여러 해에 걸쳐, 파이썬이 성숙하는 과정과 발맞추어 성장해 왔습니다. 현재 네 가지 다른 버전의 pickle 프로토콜이 있습니다.

  • 파이썬 1.x는 텍스트 기반 형식("버전 0")과 이진 형식("버전 1")의 두 가지 pickle 프로토콜을 가지고 있었습니다.
  • 파이썬 2.3에서 파이썬 클래서 객체의 새로운 기능을 다루는 새 pickle 프로토콜("버전 2")이 등장했습니다. 이진 형식으로요.
  • 파이썬 3.0에서 또 다른 pickle 프로토콜("버전 3")이 등장했습니다. 이 버전은 바이트 객체와 바이트 배열을 명시적으로 지원합니다. 역시 이진 형식입니다.

오, 보세요. 바이트와 문자열의 차이가 다시 우리의 머릿속을 복잡하게 하는군요. (처음보는 것처럼 놀라는 분... 분명 주목해서 읽지 않았군요!) 실질적으로 보자면, 파이썬 3는 pickle 프로토콜 버전 2로 저장된 데이터를 읽을 수 있지만, 파이썬 2에서는 프로토콜 버전 3 데이터를 읽어들일 수 없다는 의미입니다.

Pickle 파일 디버깅하기

Pickle 프로토콜은 어떻게 생겼을까요? 파이썬 쉘에서 잠시 떠나 우리가 만든 entry.pickle 파일을 한 번 들여다 봅시다. 그냥 한 번 보면 누가 낙서라도 해놓은 것 같습니다.

전혀 도움이 안 되는군요. 문자열이 보이긴 하지만 다른 데이터 형식은 출력되지 않거나 적어도 읽을 수는 없는 문자로 표현되어 있습니다. 이런 형식을 가지고 여러분이 직접 디버깅 하기는 좀 어렵겠네요.

가장 우리의 관심을 끄는 정보는 마지막 줄에 있습니다. 이 파일을 저장할 때 사용한 pickle 프로토콜의 버전이 기록되어 있네요. Pickle 프로토콜의 버전 정보를 명시적으로 얻을 수 있는 방법은 없습니다. Pickle 파일을 저장할 때 어떤 프로토콜이 사용되었는지 확인하려면, pickle 데이터 내에 있는 정보("opcodes")를 들여다 봐야하고 각 opcodes의 값이 어떤 프로토콜을 의미하는지 알고 있어야 합니다. pickletools.dis() 함수로 정확히 이런 일을 할 수 있습니다. 디스어셈블(disassemble)된 출력의 마지막 줄에 버전 정보를 출력하고 있습니다. 아래 함수를 이용하면 잡다한 정보는 제외하고 버전 숫자만 출력할 수도 있습니다.

pickleversion.py 다운로드 하기

사용은 이렇게 하지요.

다른 언어에서 읽을 수 있게 파이썬 객체 직렬화 하기

pickle 모듈을 이용해서 저장한 데이터 형식은 파이썬에서만 사용할 수 있습니다. 다른 언어와의 호환성은 고려하지 않고 만들어졌지요. 다른 언어와의 호환성이 중요한 문제일 경우, 다른 직렬화 형식을 찾아볼 필요가 있습니다. 그런 형식 중의 하나가 JSON입니다. "JSON"은 "JavaScript Object Notation"의 약자인데 JavaScript라는 이름에 속지마세요. JSON은 여러 프로그래밍 언어에서 사용될 수 있게 만들어졌습니다.

파이썬 3는 표준 라이브러리에 json 모듈을 포함하고 있습니다. pickle 모듈과 마찬가지로 json 모듈은 데이터 구조를 직렬화 하거나, 직렬화 된 데이터를 디스크에 저장하거나, 직렬화된 데이터를 디스크에서 불러오거나, 불러온 데이터를 역직렬화 해서 새로운 파이썬 객체로 복원하는 함수를 가지고 있습니다. 하지만 몇 가지 중요한 차이점도 있습니다. 먼저, JSON 데이터 형식은 이진 데이터가 아니라 텍스트를 기반으로 되어 있습니다. RFC 4627은 JSON 형식과 서로 다른 형식의 데이터를 어떻게 텍스트로 표현할지에 대해서 정의하고 있습니다. 예를 들어, boolean 값은 다섯 개의 문자로 이루어진 'false'나 네 개의 문자로 이루어진 'true' 둘 중 하나로 저장됩니다. 모든 JSON 값은 대소문자를 구분합니다.

두 번째로, 모든 텍스트 기반의 형식과 마찬가지로 빈 칸 처리에 대한 문제가 있습니다. JSON은 값 사이에 임의의 길이의 빈 칸(스페이스, 탭, 캐리지 리턴, 라인 피드 등)이 오는 것을 허용합니다. 이 빈 칸은 "중요하지 않습니다." JSON 인코더(encoder)는 빈 칸을 원하는 만큼 추가할 수 있고, JSON 디코더(decoder)는 값 사이의 빈 칸을 무시하도록 되어 있습니다. 그래서 JSON 데이터를 "예쁘게" 출력하는 것이 가능합니다. 한 값을 들여쓰기 해서 다른 값의 하부에 있다는 것을 보다 명확하게 드러낼 수 있고, 이런 들여쓰기는 텍스트 에디터 등에서 데이터들 읽을 때 좋은 가독성을 제공합니다. 파이썬의 json 모듈도 인코딩할 때 예쁘게 출력하는 옵션을 가지고 있습니다.

세 번째로, 끊임없이 등장하는 문자 인코딩 문제가 있습니다. JSON은 값은 평범한 텍스트(plain text)로 인코딩하는데, 여러분이 잘 알다시피 "평범한 텍스트"라는 것은 존재하지 않습니다. JSON은 반드시 유니코드 인코딩(UTF-32, UTF-16, UTF-8(기본값입니다))으로 저장되어야 하고, RFC 4627의 섹션 3에서 어떤 인코딩이 사용되고 있는지 확인하는 방법을 알 수 있습니다.

json 파일로 데이터 저장하기

json은 여러분이 자바스크립트에서 직접 데이터 구조를 정의할 때 나오는 모습과 매우 비슷하게 생겼습니다. 이것은 우연이 아닙니다. 사실 JSON으로 직렬화 된 데이터를 "디코딩"하기 위해 자바스크립트에서는 eval() 함수를 사용할 수 있습니다. (신뢰할 수 없는 입력과 관련된 문제가 존재하지만, 여기서 핵심은 JSON이 유효한 자바스크립트 식라는 점입니다.) 그래서 자바스크립트를 쓰던 분에게는 JSON이 익숙할 수도 있습니다.

  1. 이미 작성했던 entry를 다시 사용하는 대신 새로운 데이터 구조를 생성합니다. 이보다 복잡한 데이터 구조를 JSON으로 인코딩하면 어떤 일이 벌어지는지는 이 챕터의 후반부에서 보겠습니다.
  2. JSON은 텍스트에 기반을 둔 형식이므로 파일을 텍스트 모드로 열고 문자 인코딩을 지정해주어야 합니다. UTF-8을 지정하면 대체로 문제가 없겠지요.
  3. pickle 모듈과 마찬가지로 json 모듈도 파이썬 데이터 구조와 쓰기 가능한 스트림 객체를 인자로 받는 dump() 함수를 가지고 있습니다. dump() 함수는 파이썬 데이터 구조를 직렬화한 뒤 스트림 객체에 씁니다. with 문을 사용해서 작업이 끝난 뒤 파일이 자동으로 닫히게 해줍니다.

JSON 직렬화를 거친 후의 내용은 어떻게 보일까요?

확실히 pickle 파일보다 훨씬 읽기 쉽습니다. 하지만 JSON은 값과 값 사이에 임의의 빈 칸을 포함할 수 있으므로, json 모듈은 더 보기 좋은 JSON 파일을 만들 수 있는 간편한 방법을 제공합니다.

  1. json.dump() 함수에 indent 인자를 넘겨주면, 파일이 좀 커지긴 하지만 더 보기 좋은 JSON 파일을 만들 수 있습니다. indent 인자는 정수형입니다. 0은 "각 값을 다른 줄에 출력하라"는 뜻입니다. 0보다 큰 숫자를 넘겨주면, "각 값을 다른 줄에 출력하고 indent에 입력된 숫자만큼 들여쓰기를 하라"는 의미입니다.

그 결과는 다음과 같습니다.

파이썬 데이터형을 JSON으로 매핑(mapping)하기

JSON은 파이썬에만 쓰이는 것이 아니므로, 지원하는 데이터형이 파이썬의 데이터형과 일대일로 대응되지 않습니다. 어떤 데이터형은 이름만 좀 다른 정도지만, 파이썬의 두 가지 데이터형은 JSON에 아예 존재하지 않습니다. 다음 표를 보고 없는 데이터형이 어떤 것인지 한 번 찾아보세요.

Notes JSON Python 3
object dictionary
array list
string string
integer integer
real number float
* true True
* false False
* null None

* 모든 JSON 값은 대소문자를 구분합니다.

무엇이 빠져있는지 알아채셨나요? 튜플과 바이트입니다! JSON은 array 형을 가지고 있고 이는 파이썬의 리스트에 대응됩니다. 하지만 "변경할 수 없는 배열"인 튜플에 해당하는 형은 따로 가지고 있지 않습니다. 또한 JSON은 문자열을 아주 잘 지원하지만 바이트 객체나 바이트 배열은 지원하지 않습니다.

JSON에서 지원하지 않는 데이터형 직렬화하기

JSON이 바이트를 지원하지 않지만, 그렇다고 해서 바이트 객체를 직렬화할 수 없는 것은 아닙니다. json 모듈은 알 수 없는 데이터형의 인코딩과 디코딩을 위한 확장성을 제공합니다. (여기서 "알 수 없는"의 의미는 "JSON에 졍의되어 있지 않다"는 것입니다. 파이썬의 모듈이니만큼 json 모듈은 바이트 배열을 인식할 수 있지만, JSON의 세부사항에 의해 제약됩니다.) 만약 여러분이 바이트나 JSON이 지원하지 않는 데이터형을 인코딩하고 싶다면, 그 형을 처리할 수 있는 인코더와 디코더를 직접 제공해줘야 합니다.

  1. 네, 이제 전체 데이터 구조를 다시 한 번 살펴볼 시간입니다. entry는 모든 데이터형을 다 가지고 있습니다. 구제척으로 boolean, None, 문자열, 문자열의 튜플, 바이트 객체, struct_time이 있습니다.
  2. 이미 한 번 말하긴 했지만 다시 한 번 반복해야할 필요가 있습니다. JSON은 텍스트에 기반을 둔 형식입니다. JSON 파일은 항상 텍스트 모드로 여세요. 인코딩은 UTF-8입니다.
  3. 별로 좋지 않네요.무슨 일이 생긴걸까요?

이런 일이 발생했습니다. json.dump() 함수는 바이트 객체인 b'\xDE\xD5\xB4\xF8'를 직렬화 하려고 시도하지만, JSON이 바이트 객체를 지원하지 않으므로 실패합니다. 하지만, 만약 바이트를 저장하는 것이 중요하다면 여러분의 "간단한 직렬화 형식"을 정의할 수 있습니다.

customserializer.py를 다운로드 하기

  1. JSON이 지원하지 않는 데이트형을 처리하기 위해 여러분의 "간단한 직렬화 형식"을 정의하려면, 파이썬 객체를 인자로 받는 함수 하나를 선언합니다. 이 파이썬 객체는 json.dump() 함수가 스스로는 직렬화할 수 없는 객체입니다. 이 경우에는 b'\xDE\xD5\xB4\xF8'인 바이트 객체겠지요.
  2. 여러분의 직렬화 함수는 json.dump()가 전달한 파이썬 객체의 타입을 확인해야 합니다. 오직 한 종류의 데이터형을 직렬화한다면 꼭 필요한 건 아니지만, 여러분의 함수가 어떤 경우를 처리하는지 확인하기 좋고, 또 이후 새로운 데이터형을 직렬화 하기 위해 코드를 추가할 때 편리합니다.
  3. 저는 바이트 객체를 사전으로 변환하기로 했습니다. __class__ 키(key)는 원래 데이터형('bytes')을, __value__ 키는 직렬화할 때 사용할 값을 가지게 됩니다. 물론 직렬화할 때 바이트 객체를 그대로 사용할 수는 없습니다. 하고자 하는 바는 바이트 객체를 JSON이 지원하는 무언가로 변환하는 것입니다! 바이트 객체는 정수가 나열되어 있는 것이고, 각 정수는 0-255 사이의 값을 가집니다. 바이트 객체를 정수를 항목으로 하는 리스트로 바꾸기 위해 list() 함수를 사용했습니다. 그래서 b'\xDE\xD5\xB4\xF8'는 [222, 213, 180, 248]가 됩니다.(계산해보세요! 맞습니다! 바이트 \xDE는 10진수로 222이고, \xD5는 213입니다.)
  4. 이 줄은 중요합니다. 직렬화 하려고 하는 데이터 구조가 JSON이 기본으로 제공하는 직렬화 함수와 여러분이 만든 직렬화 함수 어디에서도 처리할 수 없는 데이터형을 포함하고 있을 가능성도 있습니다. 이 경우, 여러분의 함수는 처리할 수 없는 데이터형을 만났다는 의미로 TypeError를 발생시켜 json.dump()에 그 사실을 알려줘야 합니다.

  1. customserializer 모듈은 이전 예제에서 to_json() 함수를 정의한 곳입니다.
  2. 텍스트 모드, UTF-8 인코딩, 어쩌구 저쩌구. (지겹겠지만 잊어버릴 수도 있습니다. 저도 종종 잊어버려요! 실패하는 그 순간까지 잘 작동하다가 가장 예상치 못한 방식으로 실패합니다.)
  3. 중요한 부분입니다. 여러분이 만든 변환 함수를 json.dump() 함수에 끼워넣기 위해서는, 그 함수를 json.dump() 함수의 default 인자로 전달해주세요. (만세, 파이썬의 모든 것은 객체입니다!)
  4. 네, 그런데 실제로 작동하지는 않네요. 하지만 예외를 잘 살펴보세요. json.dump() 함수는 더 이상 바이트 객체를 직렬화할 수 없다고 불평하는 것이 아닙니다. 이번에는 완전히 다른 객체에 대해서 불평하고 있지요. time.struct_time 객체요.

다른 예외가 출력되는 상황에 별 진전이 없다고 느끼실지 모르겠지만, 실제로는 앞으로 많이 나아가고 있습니다. 이 문제를 해결하기 위해 단 하나의 단계만 더 나아가면 됩니다.

  1. 이미 우리가 작성했던 customserializer.to_json() 함수에 파이썬 객체(즉, json.dump() 함수가 직렬화 하지 못하는 객체)가 time.struct_time인지 아닌지 확인하는 코드를 추가해줍니다.
  2. 만약 그렇다면, 바이트 객체를 변환해줄 때와 비슷한 일을 해줍니다. time.struct_time 객체를 JSON-직렬화가 가능한 값만 포함하는 사전으로 변환합니다. 이 예제에서 datetime 객체를 JSON-직렬화가 가능한 값으로 변환하는 가장 쉬운 방법은 time.asctime() 함수를 이용해서 문자열로 변환하는 것입니다. time.asctime() 함수는 지저분하게 보이는 time.struct_time을 'Fri Mar 27 22:20:42 2009'와 같이 깔끔한 문자열로 바꿔줍니다.

이 두 가지 변환 함수와 함께라면 더 이상의 문제 없이 전체 entry 데이터 구조를 JSON으로 직렬화할 수 있습니다.

JSON 파일에서 데이터 불러오기

pickle 모듈과 마찬가지로 json 모듈도 load() 함수를 가지고 있습니다. 스트림 객체를 받아 JSON으로 인코딩된 데이터를 읽은 뒤 새로운 파이썬 객체를 반환해 JSON 데이터 구조를 복원하는 역할을 합니다.

  1. 프로그램 작동을 잘 보기 위해 파이썬 쉘 #2로 갑시다. 앞서 pickle 모듈을 이용해 만든 entry 데이터 구조를 삭제합니다.
  2. 가장 간단한 경우에 json.load() 함수는 pickle.load() 함수와 같은 방식으로 작동합니다. 스트림 객체를 인자로 넘겨주면 새로운 파이썬 객체를 돌려줍니다.
  3. 좋은 소식과 나쁜 소식이 있네요. 좋은 소식을 먼저 말씀드릴게요. json.load() 함수는 여러분이 파이썬 쉘 #1에서 만든 entry.json 파일을 잘 읽어들여 새로운 파이썬 객체를 생성했습니다. 나쁜 소식은 원래의 entry 데이터 구조를 그대로 복원해내지는 못했다는 겁니다. 'internal_id'와 'published_date'는 사전 형식으로 바뀌어 있습니다. to_json() 변환 함수에서 JSON과 호환되는 형식으로 바꾸었던 부분이죠.

json.load()는 json.dump()에 전달했던 변환 함수에 대해서 전혀 알지 못합니다. 이제 필요한 것은 to_json() 함수가 하는 일을 반대로 해주는 함수, 즉 여러분의 변환 함수를 통해 변환된 JSON 객체를 받아 원래의 파이썬 데이터형으로 변환해주는 함수입니다.

  1. 이 변환 함수 역시 인자 하나를 받고 값 하나를 반환합니다. 하지만 받아들이는 인자가 문자열이 아니고 파이썬 객체입니다. JSON으로 인코딩 된 문자열을 파이썬으로 역직렬화한 결과이지요.
  2. 여러분이 해야할 일은, 이 객체가 to_json() 함수에 의해 만들어진 '__class__' 키를 포함하고 있는지 확인하는 것입니다. 만약 포함하고 있다면, '__class__' 키의 값을 통해 어떻게 원래의 파이썬 데이터형으로 바꾸어야 할지 알 수 있습니다.
  3. time.asctime() 함수가 만들어 낸 시간 문자열을 복원하기 위해서 time.strptime() 함수를 사용할 수 있습니다. 이 함수는 일정한 형식을 가진 datetime 문자열(원하는 형식을 지정할 수도 있지만 여기서는 time.asctime()의 기본 형식을 사용합니다)을 인자로 받고 time.struct_time을 반환합니다.
  4. 정수를 항목으로 하는 리스트를 바이트 객체로 복원하기 위해 bytes() 함수를 사용할 수 있습니다.

이것으로 끝이군요. to_json() 함수에서 처리한 데이터형이 두 가지인데, 그 두 가지 모두 from_json() 함수에서 처리하고 있습니다. 결과를 한 번 볼까요.

  1. 역직렬화 과정에 from_json() 함수를 끼워넣기 위해서는 json.load() 함수의 object_hook 인자로 넘겨주면 됩니다. 함수를 인자로 받는 함수입니다. 아주 편리하네요!
  2. 이제 entry 데이터 구조는 값이 바이트 객체인 'internal_id' 키를 가지고 있습니다. 값이 time.struct_time 객체인 'published_date' 키도 마찬가지네요.

하지만 마지막으로 짚고 넘어가야 할 부분이 있습니다.

  1. to_json()와 from_json() 함수를 직렬화/역직렬화 과정에 집어넣긴 했지만, 원본 데이터 구조를 완벽하게 복원하지는 못했습니다. 이유가 뭘까요?
  2. 원본 entry 데이터 구조에서 'tag' 키는 세 문자열을 항목으로 가지는 튜플이었습니다.
  3. 하지만 직렬화/역직렬화 과정을 거친 entry2의 데이터 구조에서 'tag' 키는 세 문자열을 항목으로 가지는 리스트입니다. JSON은 튜플과 리스트를 구별하지 않습니다. 단지 array라는 리스트 비슷한 데이터형 하나를 가집니다. 그리고 json 모듈은 직렬화 과정에서 튜플과 리스트 형을 JSON의 array 형으로 조용히 바꾸어 버립니다. 대부분의 경우 튜플과 리스트 사이의 차이는 무시할 수 있습니다. 하지만 json 모듈을 쓸 때는 항상 차이가 있다는 사실을 유념해두는 것이 좋습니다.

더 읽을거리

pickle 모듈을 다루는 많은 글이 cPickle에 대해 언급하고 있습니다. 파이썬 2에서는 pickle 모듈을 구현하는 두 가지 방법이 있었습니다. 하나는 순수 파이썬으로 작성한 것이고, 다른 하나는 C로 작성한 것입니다(그래도 파이썬에서 호출할 수 있습니다). 파이썬 3로 들어서면서 이 두 모듈을 하나로 합쳐쳤습니다. 그래서 그냥 import pickle이라고 쓰면 모듈을 불러낼 수 있었지요. 이런 글들이 유용할 수도 있지만, 파이썬 3를 쓴다면 이제 cPickle에 대해서는 잊어버려도 괜찮을겁니다.

pickle 모듈에 대한 글:

JSON과 json 모듈에 대한 글:

pickle의 확장과 관련된 글:


이 강의는 영어로 된 원문을 기초로 작성되었으며, Creative Commons Attribution Share-Alike 라이센스하에 자유로운 변경, 배포가 가능합니다

10384 읽음
이전 챕터 12. XML
다음 챕터 14. HTTP 웹서비스

저자

토론이 없습니다

Please log in to leave a comment

16.5.11618.20190612.allo
문제 보고 · 사용조건 · 개인정보보호
래블업 주식회사 · 대한민국 서울 테헤란로 145 · 대표: 신정규 · 사업자번호: 864-88-00080 · +82 70-8200-2587

거절 확인

닫기
좋아요 책갈피 토론

아래 주소를 복사하세요