深入Python3(十二) 序列化Python对象

0.摘要

  序列化的概念很简单。内存里面有一个数据结构,你希望将它保存下来,重用,或者发送给其他人。你会怎么做?嗯, 这取决于你想要怎么保存,怎么重用,发送给谁。很多游戏允许你在退出的时候保存进度,然后你再次启动的时候回到上次退出的地方。(实际上, 很多非游戏程序也会这么干。) 在这个情况下, 一个捕获了当前进度的数据结构需要在你退出的时候保存到磁盘上,接着在你重新启动的时候从磁盘上加载进来。这个数据只会被创建它的程序使用,不会发送到网络上,也不会被其它程序读取。因此,互操作的问题被限制在保证新版本的程序能够读取以前版本的程序创建的数据。

  在这种情况下, p i c k l e pickle pickle 模块是理想的。它是 P y t h o n Python Python标准库的一部分, 所以它总是可用的。它很快; 它的大部分内容同 P y t h o n Python Python解释器本身一样是用 C C C写的。 它可以存储任意复杂的 P y t h o n Python Python数据结构。

   什么东西能用 p i c k l e pickle pickle模块存储?

  • 所有 P y t h o n Python Python支持的原生类型 : 布尔, 整数, 浮点数, 复数, 字符串, b y t e s bytes bytes(字节串)对象, 字节数组,
    以及 N o n e None None.
  • 由任何原生类型组成的列表,元组,字典和集合。
  • 由任何原生类型组成的列表,元组,字典和集合组成的列表,元组,字典和集合(可以一直嵌套下去,直至 P y t h o n Python Python支持的最大递归层数).
  • 函数,类,和类的实例。

  本章会使用两个 P y t h o n   S h e l l Python\ Shell Python Shell来讲故事。本章的例子都是一个单独的故事的一部分。当我演示 p i c k l e pickle pickle j s o n json json 模块时,你会被要求在两个 P y t h o n   S h e l l Python\ Shell Python Shell中来回切换。
在这里插入图片描述

1.保存数据到 Pickle 文件

在这里插入图片描述
在这里插入图片描述

  ①仍然在 P y t h o n   S h e l l   # 1 Python\ Shell\ \#1 Python Shell #1 中。
  ②使用 o p e n ( ) open() open() 函数来打开一个文件。设置文件模式为’wb’来以二进制写模式打开文件。把它放入 w i t h with with 语句中来保证在你完成的时候文件自动被关闭。
  ③ p i c k l e pickle pickle模块中的 d u m p ( ) dump() dump()函数接受一个可序列化的 P y t h o n Python Python 数据结构, 使用最新版本的 p i c k l e pickle pickle协议将其序列化为一个二进制的, P y t h o n Python Python特定的格式, 并且保存到一个打开的文件里。

  最后一句话很重要。

  • p i c k l e pickle pickle模块接受一个 P y t h o n Python Python数据结构并将其保存到一个文件中。
  • 要做到这样,它使用一个被称为“ p i c k l e pickle pickle协议”的东西序列化该数据结构。
  • p i c k l e pickle pickle 协议是 P y t h o n Python Python特定的,没有任何跨语言兼容的保证。你很可能不能使用 P e r l Perl Perl, p h p php php, J a v a Java Java,
    或者其他语言来对你刚刚创建的 e n t r y . p i c k l e entry.pickle entry.pickle文件做任何有用的事情。
  • 并非所有的 P y t h o n Python Python数据结构都可以通过 p i c k l e pickle pickle模块序列化。随着新的数据类型被加入到 P y t h o n Python Python语言中, p i c k l e pickle pickle协议已经被修改过很多次了,但是它还是有一些限制。
  • 由于这些变化,不同版本的 P y t h o n Python Python的兼容性也没有保证。新的版本的 P y t h o n Python Python支持旧的序列化格式,但是旧版本的 P y t h o n Python Python不支持新的格式(因为它们不支持新的数据类型)。
  • 除非你指定, p i c k l e pickle pickle模块中的函数将使用最新版本的 p i c k l e pickle pickle协议。这保证了你对可以被序列化的数据类型有最大的灵活度,但这也意味着生成的文件不能被不支持新版 p i c k l e pickle pickle协议的旧版本的 P y t h o n Python Python读取。
  • 最新版本的 p i c k l e pickle pickle协议是二进制格式的。请确认使用二进制模式来打开你的 p i c k l e pickle pickle文件,否则当你写入的时候数据会被损坏。

2.从Pickle文件读取数据

在这里插入图片描述

  ①这是 P y t h o n   S h e l l   # 2 Python\ Shell\ \#2 Python Shell #2
  ②这里 e n t r y entry entry 变量没有被定义过。你在 P y t h o n   S h e l l   # 1 Python\ Shell\ \#1 Python Shell #1 中定义了 e n t r y entry entry变量, 但是那是另一个拥有自己状态的完全不同的环境。
  ③打开你之前创建的 e n t r y . p i c k l e entry.pickle entry.pickle文件。 p i c k l e pickle pickle模块使用二进制数据格式,所以你总是应该使用二进制模式打开 p i c k l e pickle pickle文件。
  ④ p i c k l e . l o a d ( ) pickle.load() pickle.load()函数接受一个流对象, 从流中读取序列化后的数据,创建一个新的 P y t h o n Python Python对象,在新的 P y t h o n Python Python对象中重建被序列化的数据,然后返回新建的 P y t h o n Python Python对象。
  ⑤现在 e n t r y entry entry变量是一个键和值看起来都很熟悉的字典。
在这里插入图片描述

  ①切换回 P y t h o n   S h e l l   # 1 Python\ Shell\ \#1 Python Shell #1
  ②打开 e n t r y . p i c k l e entry.pickle entry.pickle文件。
  ③将序列化后的数据装载到一个新的变量 e n t r y 2 entry2 entry2中。
  ④ P y t h o n Python Python 确认两个字典 e n t r y entry entry e n t r y 2 entry2 entry2 是相等的。在这个 s h e l l shell shell里, 你从零开始构造了 e n t r y entry entry, 从一个空字典开始然后手动给各个键赋值。你序列化了这个字典并将其保存在 e n t r y . p i c k l e entry.pickle entry.pickle文件中。现在你从文件中读取序列化后的数据并创建了原始数据结构的一个完美复制品。
  ⑤相等和相同是不一样的。我说的是你创建了原始数据结构的一个完美复制品, 这没错。但它仅仅是一个复制品。
  ⑥我要指出 t a g s tags tags键对应的值是一个元组,而 i n t e r n a l _ i d internal\_id internal_id键对应的值是一个 b y t e s bytes bytes对象。原因在这章的后面就会清楚了。

3.不使用文件来进行序列化

  前一节中的例子展示了如果将一个 P y t h o n Python Python对象序列化到磁盘文件。但如果你不想或不需要文件呢?你也可以序列化到一个内存中的 b y t e s bytes bytes对象。
在这里插入图片描述

  ① p i c k l e . d u m p s ( ) pickle.dumps() pickle.dumps()函数(注意函数名最后的’s’)执行和 p i c k l e . d u m p ( ) pickle.dump() pickle.dump()函数相同的序列化。后者接受流对象并将序列化后的数据保存到磁盘文件,这个函数简单的返回序列化的数据。
  ②由于 p i c k l e pickle pickle协议使用一个二进制数据格式,所以 p i c k l e . d u m p s ( ) pickle.dumps() pickle.dumps()函数返回 b y t e s bytes bytes对象。
  ③ p i c k l e . l o a d s ( ) pickle.loads() pickle.loads()函数(再一次, 注意函数名最后的’s’) 执行和 p i c k l e . l o a d ( ) pickle.load() pickle.load()函数一样的反序列化。后者接受一个流对象并去文件读取序列化后的数据,它接受包含序列化后的数据的 b y t e s bytes bytes对象, 比如 p i c k l e . d u m p s ( ) pickle.dumps() pickle.dumps()函数返回的对象。
  ④最终结果是一样的: 原始字典的完美复制。

4.字节和字符串又一次抬起了它们丑陋的头

在这里插入图片描述

  在实践中这意味着, 尽管 P y t h o n 3 Python 3 Python3 可以读取版本 2 2 2 p i c k l e pickle pickle 协议生成的数据, P y t h o n 2 Python 2 Python2 不能读取版本 3 3 3的协议生成的数据。

5.调试Pickle 文件

在这里插入图片描述

  这不是很有用。你可以看见字符串,但是其他数据类型显示为不可打印的(或者至少是不可读的)字符。域之间没有明显的分隔符(比如跳格符或空格)。你肯定不希望来调试这样一个格式。在这里插入图片描述
在这里插入图片描述

  这个反汇编中最有趣的信息是最后一行, 因为它包含了文件保存时使用的 p i c k l e pickle pickle协议的版本号。在 p i c k l e pickle pickle协议里面没有明确的版本标志。为了确定保存 p i c k l e pickle pickle文件时使用的协议版本,你需要查看序列化后的数据的标记( o p c o d e s opcodes opcodes)。 p i c k l e . d i s ( ) pickle.dis() pickle.dis()函数正是这么干的,并且它在反汇编的输出的最后一行打印出结果。下面是一个不打印,仅仅返回版本号的函数:
在这里插入图片描述

6.序列化Python对象以供其它语言读取

   p i c k l e pickle pickle模块使用的数据格式是 P y t h o n Python Python特定的。它没有做任何兼容其它编程语言的努力。如果跨语言兼容是你的需求之一,你得去寻找其它的序列化格式。一个这样的格式是 j s o n json json j s o n json json 代表 J a v a S c r i p t   O b j e c t   N o t a t i o n JavaScript\ Object\ Notation JavaScript Object Notation, 但是不要让名字糊弄你 — j s o n json json 是被设计为跨语言使用的。

   P y t h o n 3 Python 3 Python3 在标准库中包含了一个 j s o n json json模块。同 p i c k l e pickle pickle模块类似, j s o n json json模块包含一些函数,可以序列化数据结构,保存序列化后的数据至磁盘,从磁盘上读取序列化后的数据,将数据反序列化成新的 P y t h o n Python Python对象。但两者也有一些很重要的区别。 首先, j s o n json json数据格式是基于文本的, 不是二进制的。RFC 4627 定义了 j s o n json json格式以及怎样将各种类型的数据编码成文本。比如,一个布尔值要么存储为5个字符的字符串 f a l s e false false,要么存储为4个字符的字符串 t r u e true true。 所有的 j s o n json json值都是大小写敏感的。

  第二,由于是文本格式, 存在空白(whitespaces)的问题。 j s o n json json 允许在值之间有任意数目的空白(空格, 跳格, 回车,换行)。空白是“无关紧要的”,这意味着 j s o n json json编码器可以按它们的喜好添加任意多或任意少的空白, 而 j s o n json json解码器被要求忽略值之间的任意空白。这允许你“美观的打印(pretty-print)” 你的 j s o n json json 数据, 通过不同的缩进层次嵌套值,这样你就可以在标准浏览器或文本编辑器中阅读它。 P y t h o n Python Python j s o n json json 模块有在编码时执行美观打印(pretty-printing)的选项。

  第三,字符编码的问题是长期存在的。 j s o n json json 用纯文本编码数据, 但是你知道, “不存在纯文本这种东西。” j s o n json json必须以 U n i c o d e Unicode Unicode 编码( U T F − 32 UTF-32 UTF32, U T F − 16 UTF-16 UTF16, 或者默认的, u t f − 8 utf-8 utf8)方式存储, RFC 4627的第3节 定义了如何区分使用的是哪种编码。

7.将数据保存至 json 文件

   j s o n json json 看起来非常像你在 J a v a s c r i p t Javascript Javascript中手工定义的数据结构。这不是意外; 实际上你可以使用 J a v a S c r i p t JavaScript JavaScript e v a l ( ) eval() eval()函数来“解码” j s o n json json序列化过的数据。
在这里插入图片描述

  ①我们将创建一个新的数据结构,而不是重用现存的 e n t r y entry entry数据结构。在这章的后面, 我们将会看见当我们试图用 j s o n json json编码更复杂的数据结构的时候会发生什么。
  ② j s o n json json 是一个基于文本的格式, 这意味你可以以文本模式打开文件,并给定一个字符编码。用 u t f − 8 utf-8 utf8总是没错的。
  ③同 p i c k l e pickle pickle模块一样, j s o n json json 模块定义了 d u m p ( ) dump() dump()函数,它接受一个 P y t h o n Python Python 数据结构和一个可写的流对象。 d u m p ( ) dump() dump() 函数将 P y t h o n Python Python数据结构序列化并写入到流对象中。在 w i t h with with语句内工作保证当我们完成的时候正确的关闭文件。
在这里插入图片描述

  ①如果你给 j s o n . d u m p ( ) json.dump() json.dump()函数传入 i n d e n t indent indent参数, 它以文件变大为代价使生成的 j s o n json json文件更可读。 i n d e n t indent indent 参数是一个整数。 0 0 0 意味着“每个值单独一行。” 大于 0 0 0的数字意味着“每个值单独一行并且使用这个数目的空格来缩进嵌套的数据结构。”
在这里插入图片描述

8.将Python数据类型映射到json

  由于 j s o n json json 不是 P y t h o n Python Python特定的,对应到 P y t h o n Python Python的数据类型的时候有很多不匹配。有一些仅仅是名字不同,但是有两个 P y t h o n Python Python数据类型完全缺少。看看你能能把它们指出来:

Json Python 3
Object Dictionary
Array List
String String
Integer Integer
Real Number Float
true True
false False
null None

  注意到什么被遗漏了吗?元组和字节串( b y t e s bytes bytes)! j s o n json json 有数组类型, j s o n json json 模块将其映射到 P y t h o n Python Python的列表, 但是它没有一个单独的类型对应 “冻结数组(frozen arrays)” (元组)。而且尽管 j s o n json json 非常好的支持字符串,但是它没有对 b y t e s bytes bytes 对象或字节数组的支持。

9.序列化json不支持的数据类型

  即使 j s o n json json没有内建的字节流支持, 并不意味着你不能序列化 b y t e s bytes bytes对象。 j s o n json json模块提供了编解码未知数据类型的扩展接口。(“未知”的意思是 j s o n json json没有定义,很显然 j s o n json json 模块认识字节数组, 但是它被 j s o n json json规范的限制束缚住了。) 如果你希望编码字节串或者其它 j s o n json json没有原生支持的数据类型,你需要给这些类型提供定制的编码和解码器。
在这里插入图片描述

  情况是这样的: j s o n . d u m p ( ) json.dump() json.dump() 函数试图序列化 b y t e s bytes bytes对象 b’\xDE\xD5\xB4\xF8’,但是它失败了,原因是 j s o n json json 不支持 b y t e s bytes bytes对象。然而, 如果保存字节串对你来说很重要,你可以定义自己的“迷你序列化格式。”
在这里插入图片描述

  ①为了给一个 j s o n json json没有原生支持的数据类型定义你自己的“迷你序列化格式”, 只要定义一个接受一个 P y t h o n Python Python对象为参数的函数。这个对象将会是 j s o n . d u m p ( ) json.dump() json.dump()函数无法自己序列化的实际对象 — 这个例子里是 b y t e s bytes bytes 对象 b’\xDE\xD5\xB4\xF8’。
  ②你的自定义序列化函数应该检查 j s o n . d u m p ( ) json.dump() json.dump()函数传给它的对象的类型。当你的函数只序列化一个类型的时候。这不是必须的,但是它使你的函数的覆盖的内容清楚明白,并且在你需要序列化更多类型的时候更容易扩展。
  ③在这个例子里面, 我将 b y t e s bytes bytes 对象转换成字典。 _ _ c l a s s _ _ \_\_class\_\_ __class__ 键持有原始的数据类型(以字符串的形式, ‘bytes’), 而 _ _ v a l u e _ _ \_\_value\_\_ __value__ 键持有实际的数据。当然它不能是 b y t e s bytes bytes对象; 大体的想法是将其转换成某些可以被 j s o n json json序列化的东西! b y t e s bytes bytes对象就是一个范围在0–255的整数的序列。 我们可以使用 l i s t ( ) list() list() 函数将 b y t e s bytes bytes对象转换成整数列表。所以b’\xDE\xD5\xB4\xF8’ 变成 [222, 213, 180, 248]。(算一下! 这是对的! 16进制的字节 \xDE 是十进制的 222, \xD5 是 213, 以此类推。)
  ④这一行很重要。你序列化的数据结构可能包含 j s o n json json内建的可序列化类型和你的定制序列化器支持的类型之外的东西。在这种情况下,你的定制序列化器抛出一个 T y p e E r r o r TypeError TypeError,那样 j s o n . d u m p ( ) json.dump() json.dump() 函数就可以知道你的定制序列化函数不认识该类型。
  就这么多;你不需要其它的东西。特别是, 这个定制序列化函数返回 P y t h o n Python Python字典,不是字符串。你不是自己做所有序列化到 j s o n json json的工作; 你仅仅在做转换成被支持的类型那部分工作。 j s o n . d u m p ( ) json.dump() json.dump() 函数做剩下的事情。
在这里插入图片描述

  ① c u s t o m s e r i a l i z e r customserializer customserializer 模块是你在前一个例子中定义 t o _ j s o n ( ) to\_json() to_json()函数的地方。
  ②文本模式, u t f − 8 utf-8 utf8 编码。(你很可能会忘记这一点! 我就忘记过好几次! 事情一切正常直到它失败的时刻, 而它的失败很令人瞩目。)
  ③这是重点: 为了将定制转换函数钩子嵌入 j s o n . d u m p ( ) json.dump() json.dump()函数, 只要将你的函数以 d e f a u l t default default参数传入 j s o n . d u m p ( ) json.dump() json.dump()函数。(万岁, P y t h o n Python Python里一切皆对象!)
  ④好吧, 实际上还是不能工作。但是看一下异常。 j s o n . d u m p ( ) json.dump() json.dump() 函数不再抱怨无法序列化 b y t e s bytes bytes对象了。现在它在抱怨另一个完全不同的对象: t i m e . s t r u c t _ t i m e time.struct\_time time.struct_time 对象。
在这里插入图片描述

  ①在现存的 c u s t o m s e r i a l i z e r . t o _ j s o n ( ) customserializer.to\_json() customserializer.to_json()函数里面, 我们加入了 P y t h o n Python Python 对象 ( j s o n . d u m p ( ) json.dump() json.dump() 处理不了的那些) 是不是 t i m e . s t r u c t _ t i m e time.struct\_time time.struct_time的判断。
  ②如果是的,我们做一些同处理 b y t e s bytes bytes对象时类似的事情来转换: 将 t i m e . s t r u c t _ t i m e time.struct\_time time.struct_time 结构转化成一个只包含 j s o n json json可序列化值的字典。在这个例子里, 最简单的将日期时间转换成 j s o n json json可序列化值的方法是使用 t i m e . a s c t i m e ( ) time.asctime() time.asctime()函数将其转换成字符串。 t i m e . a s c t i m e ( ) time.asctime() time.asctime() 函数将难看的 t i m e . s t r u c t _ t i m e time.struct\_time time.struct_time 转换成字符串 ‘Fri Mar 27 22:20:42 2009’。
在这里插入图片描述

10.从json文件加载数据

  类似 p i c k l e pickle pickle 模块, j s o n json json模块有一个 l o a d ( ) load() load()函数接受一个流对象,从中读取 j s o n json json编码过的数据, 并且创建该 j s o n json json数据结构的 P y t h o n Python Python对象的镜像。
在这里插入图片描述

  ①为了演示目的,切换到 P y t h o n   S h e l l   # 2 Python\ Shell\ \#2 Python Shell #2 并且删除在这一章前面使用 p i c k l e pickle pickle模块创建的 e n t r y entry entry数据结构。
  ②最简单的情况下, j s o n . l o a d ( ) json.load() json.load()函数同 p i c k l e . l o a d ( ) pickle.load() pickle.load()函数的结果一模一样。你传入一个流对象,它返回一个新的 P y t h o n Python Python对象。
  ③有好消息也有坏消息。好消息先来: j s o n . l o a d ( ) json.load() json.load() 函数成功的读取了你在 P y t h o n   S h e l l   # 1 Python\ Shell\ \#1 Python Shell #1中创建的 e n t r y . j s o n entry.json entry.json文件并且生成了一个包含那些数据的新的 P y t h o n Python Python对象。接着是坏消息: 它没有重建原始的 e n t r y entry entry 数据结构。‘internal_id’ 和 ‘published_date’ 这两个值被重建为字典 — 具体来说, 你在 t o _ j s o n ( ) to\_json() to_json()转换函数中使用 j s o n json json兼容的值创建的字典。

   j s o n . l o a d ( ) json.load() json.load() 并不知道你可能传给 j s o n . d u m p ( ) json.dump() json.dump()的任何转换函数的任何信息。你需要的是 t o _ j s o n ( ) to\_json() to_json()函数的逆函数 — 一个接受定制转换出的 j s o n json json 对象并将其转换回原始的 P y t h o n Python Python数据类型。
在这里插入图片描述

  ①这函数也同样接受一个参数返回一个值。但是参数不是字符串,而是一个 P y t h o n Python Python对象 — 反序列化一个 j s o n json json编码的字符串为 P y t h o n Python Python的结果。
  ②你只需要检查这个对象是否包含 t o j s o n ( ) to_json() tojson()函数创建的 _ _ c l a s s _ _ \_\_class\_\_ __class__键。如果是的, _ _ c l a s s _ _ \_\_class\_\_ __class__键对应的值将告诉你如何将值解码成原来的 P y t h o n Python Python数据类型。
  ③为了解码由 t i m e . a s c t i m e ( ) time.asctime() time.asctime()函数返回的字符串,你要使用 t i m e . s t r p t i m e ( ) time.strptime() time.strptime()函数。这个函数接受一个格式化过的时间字符串(格式可以自定义,但默认值同 t i m e . a s c t i m e ( ) time.asctime() time.asctime()函数的默认值相同) 并且返回 t i m e . s t r u c t _ t i m e time.struct\_time time.struct_time
  ④为了将整数列表转换回 b y t e s bytes bytes 对象, 你可以使用 b y t e s ( ) bytes() bytes() 函数。
在这里插入图片描述

  ①为了将 f r o m _ j s o n ( ) from\_json() from_json()函数嵌入到反序列化过程中,把它作为 o b j e c t _ h o o k object\_hook object_hook 参数传入到 j s o n . l o a d ( ) json.load() json.load()函数中。接受函数作为参数的函数; 真方便!
  ② e n t r y entry entry数据结构现在有一个值为 b y t e s bytes bytes对象的’internal_id’键。它也包含一个’published_date’键,其值为 t i m e . s t r u c t t i m e time.struct_time time.structtime对象。
在这里插入图片描述

  ①即使在序列化过程中加入了 t o _ j s o n ( ) to\_json() to_json()钩子函数, 也在反序列化过程中加入 f r o m _ j s o n ( ) from\_json() from_json()钩子函数, 我们仍然没有重新创建原始数据结构的完美复制品。为什么没有?
  ②在原始的 e n t r y entry entry 数据结构中, 'tags’键的值为一个三个字符串组成的元组。
  ③但是重现创建的 e n t r y 2 entry2 entry2 数据结构中, ‘tags’ 键的值是一个三个字符串组成的列表。 j s o n json json 并不区分元组和列表;它只有一个类似列表的数据类型,数组,并且 j s o n json json模块在序列化过程中会安静的将元组和列表两个都转换成 j s o n json json 数组。大多数情况下,你可以忽略元组和列表的区别,但是在使用 j s o n json json 模块时应记得有这么一回事。
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

猜你喜欢

转载自blog.csdn.net/xiji333/article/details/110825518
今日推荐