ホームページ  >  記事  >  バックエンド開発  >  Pythonのデータシリアル化(json、pickle、shelve)の詳細説明

Pythonのデータシリアル化(json、pickle、shelve)の詳細説明

Y2J
Y2Jオリジナル
2017-04-20 09:58:057266ブラウズ

この記事では主に Python のデータシリアル化について紹介します。このセクションでは、データシリアル化のための Python の組み込みモジュールをいくつか紹介します。

1. はじめに

1. 現実的なニーズ

各プログラミング言語には独自のデータ型があり、その中でオブジェクト指向プログラミング言語では、開発者がデータ型 (カスタムなど) をカスタマイズすることもできます。クラスの定義)、Python にも同じことが当てはまります。多くの場合、次のようなニーズがあります。

  1. メモリ内のさまざまな種類のデータをネットワーク経由で他のマシンまたはクライアントに送信します。

  2. さまざまな種類のデータをメモリにローカルに保存します。

2.データ形式

システム内のデータをネットワーク経由で他のシステムやクライアントに送信したい場合、通常、データを文字列または単語の最初の文字列に変換する必要があり、統一されたデータ形式を指定する必要があります。データ受信側は、これらのデータの意味を正しく解析して理解できます。 XML は初期には広く使用されていたデータ交換形式であり、初期のシステム統合に関する論文でよく見られましたが、現在ではより頻繁に使用されているデータ交換形式は、軽量のデータ交換形式である JSON (Javascript Object Notation) です。 XML と比較すると、JSON はシンプルで読み書きが容易で、マシンによる解析や生成も容易です。これに加えて、内部で使用されるデータ交換形式をカスタマイズすることもできます。

データをローカル ディスクに永続化したい場合、データのこの部分は通常、システムの内部使用のみを目的としているため、データ変換プロトコルと変換されたデータ形式を標準化して統一する必要はありません。システムが内部的にそれを正しく識別できる限り、それだけです。ただし、システム内の変換プロトコルは通常、プログラミング言語のバージョンのアップグレードに応じて変更されるため(アルゴリズムの改善、効率の向上)、通常は変換プロトコルとプログラミング言語のバージョン間の互換性の問題が発生します。次のようなピクルスプロトコルがそれにあたります。一例です。

3. シリアル化/逆シリアル化

ネットワーク経由で送信したり、ローカル ディスクに保存したりできるデータ形式 (XML、JSON、または特定の形式のバイト文字列など) にオブジェクトを変換するプロセス。 ) が呼び出される場合はシリアル化され、それ以外の場合は逆シリアル化と呼ばれます。

4. 関連モジュール

このセクションでは、データシリアル化のための Python のいくつかの組み込みモジュールを紹介します。

ジソン Python データ型と汎用 (json) 文字列間の変換を実装するために使用されます は、Python データ型と Python 間の変換を実装するために使用されます- 固有のバイナリ形式dumps()、dump()、loads()、load()open()
dumps()、dump()、loads()、load() pickle
shelve は、特にPythonデータ型をディスク、シェルフに永続化するために使用されます。操作が便利
2. jsonモジュール
ほとんどのプログラミング言語には、jsonデータを処理するためのインターフェースが追加されています。内蔵モジュールなのでダウンロードせずに使用できます。 1. シリアル化と逆シリアル化
Python の JSON モジュール シリアル化と逆シリアル化のプロセスは、それぞれエンコードとデコードと呼ばれます。

エンコーディング: Python オブジェクトを JSON 文字列に変換します

デコーディング: JSON 文字列を Python オブジェクトに変換します

json モジュールは、シリアル化および逆シリアル化操作のために次の 2 つのメソッドを提供します

    # 序列化:将Python对象转换成json字符串
    dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
    
    # 反序列化:将json字符串转换成Python对象
    loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
  1. でさらに、json モジュールは、シリアル化された json データをファイルに直接保存し、変換操作のためにファイル内の json データを直接読み取ることを可能にする 2 つの追加メソッドも提供します:

  2. # 序列化:将Python对象转换成json字符串并存储到文件中
    dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
    
    # 反序列化:读取指定文件中的json字符串并转换成Python对象
    load(fp, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)

2。 JSONとPythonのデータ型の対応


PythonからJSON


Python


JSON

dict

Object

list, tuple配列str stringint、float、intおよびfloatから派生したEnumsnumbersTruetru​​eFalsefalseNoneヌル

JSON から Python

JSON Python
オブジェクト dict
配列 リスト
文字列 str
number(int) int
数値(実数) float
tru​​e True
false False
null None

说明:

  1. Python dict中的非字符串key被转换成JSON字符串时都会被转换为小写字符串;

  2. Python中的tuple,在序列化时会被转换为array,但是反序列化时,array会被转化为list;

  3. 由以上两点可知,当Python对象中包含tuple数据或者包含dict,且dict中存在非字符串的key时,反序列化后得到的结果与原来的Python对象是不一致的;

  4. 对于Python内置的数据类型(如:str, unicode, int, float, bool, None, list, tuple, dict)json模块可以直接进行序列化/反序列化处理;对于自定义类的对象进行序列化和反序列化时,需要我们自己定义一个方法来完成定义object和dict之间进行转化。

3. 实例:内置数据类型序列化/反序列化

序列化


# 序列化
>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)})
'{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}'

sort_keys参数: 表示序列化时是否对dict的key进行排序(dict默认是无序的)


# 序列化并对key进行排序
>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, sort_keys=True)
'{"a": "str", "b": 11.1, "c": true, "d": null, "e": 10, "f": [1, 2, 3], "g": [4, 5, 6]}'

indent参数: 表示缩进的意思,它可以使得数据存储的格式变得更加优雅、可读性更强;如果indent是一个非负整数或字符串,则JSON array元素和object成员将会被以相应的缩进级别进行打印输出;如果indent是0或负数或空字符串,则将只会插入换行,不会有缩进。


# 序列化并对key进行排序及格式化输出
>>> print(json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, sort_keys=True, indent=4)) 
{
 "a": "str", 
 "b": 11.1, 
 "c": true, 
 "d": null, 
 "e": 10, 
 "f": [
  1, 
  2, 
  3
 ], 
 "g": [
  4, 
  5, 
  6
 ]
}

separators参数: 尽管indent参数可以使得数据存储的格式变得更加优雅、可读性更强,但是那是通过添加一些冗余的空白字符进行填充的。当json被用于网络数据通信时,应该尽可能的减少无用的数据传输,这样可以节省贷款并加快数据传输速度。json模块序列化Python对象后得到的json字符串中的','号和':'号分隔符后默认都会附加一个空白字符,我们可以通过separators参数重新指定分隔符,从而去除无用的空白字符;

  1. 该参数的值应该是一个tuple(item_separator, key_separator)

  2. 如果indent是None,其默认值为(', ', ': ')

  3. 如果indent不为None,则默认值为(',', ': ')

  4. 我们可以通过为separator赋值为(',', ':')来消除空白字符


>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)})
'{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}'

>>> json.dumps({'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, separators=(',',':'))
'{"a":"str","c":true,"b":11.1,"e":10,"d":null,"g":[4,5,6],"f":[1,2,3]}'

反序列化


# 反序列化
>>> json.loads('{"a": "str", "c": true, "b": 11.1, "e": 10, "d": null, "g": [4, 5, 6], "f": [1, 2, 3]}')
{'c': True, 'e': 10, 'a': 'str', 'g': [4, 5, 6], 'd': None, 'f': [1, 2, 3], 'b': 11.1}

>>> json.loads('{"a":"str","c":true,"b":11.1,"e":10,"d":null,"g":[4,5,6],"f":[1,2,3]}')
{'c': True, 'e': 10, 'a': 'str', 'g': [4, 5, 6], 'd': None, 'f': [1, 2, 3], 'b': 11.1}

dump()与load()函数示例


# 序列化到文件中
>>> with open('test.json', 'w') as fp:
...  json.dump({'a':'str中国', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}, fp, indent=4)

# 反序列化文件中的内容
>>> with open('test.json', 'r') as fp:
...  json.load(fp)
{'e': 10, 'g': [4, 5, 6], 'b': 11.1, 'c': True, 'd': None, 'a': 'str中国', 'f': [1, 2, 3]}

需要说明的是: 如果试图使用相同的fp重复调用dump()函数去序列化多个对象(或序列化同一个对象多次),将会产生一个无效的JSON文件,也就是说对于一个fp只能调用一次dump()。

4. 实例:自定义数据类型的序列化/反序列化

Python是面向对象的编程语言,我们可以自定义需要的数据类型;实际工作中,我们常常会用到自定义数据类型的序列化与反序列化操作。要实现自定义数据类型的序列化与反序列化有两种方式:

  1. 通过转换函数实现

  2. 通过继承JSONEncoder和JSONDecoder类实现

首先来自定义一个数据类型


class Student(object):
 def init(self, name, age, sno):
  self.name = name
  self.age = age
  self.sno = sno
 
 def repr(self):
  return 'Student [name: %s, age: %d, sno: %d]' % (self.name, self.age, self.sno)

直接调用dumps()方法会引发TypeError错误:


>>> stu = Student('Tom', 19, 1)
>>> print(stu)
Student [name: Tom, age: 19, sno: 1]
>>>
>>> json.dumps(stu)
...
TypeError: Student [name: Tom, age: 19, sno: 1] is not JSON serializable

上面的异常信息中指出:stu对象不可以被序列化为JSON个数的数据。那么我们分别通过“编写转换函数” 和 “继承JSONEncoder和JSONDecoder类” 来实现对这个自定义数据类型的JSON序列化和反序列化。

方法1:编写转换函数

那么这个转换函数要完成哪两个数据类型之间的转换呢? 从上面列出的JSON与Python数据类型的对应表中可知,JSON中的object对应的是Python中的dict,因此要对Python中的自定义数据类型的对象进行序列化,就需要先把这个对象转换成json模块可以直接进行序列化dict类型。由此可知,这个转换函数是要完成的是Python对象(不是JSON对象)与dict之间的相互转换,且序列化时转换过程是“Python对象 --> dict --> JSON object”,反序列化的过程是“JSON object -> dict --> Python对象”。所以,我们需要编写两个转换函数来分别实现序列化和反序列化时的转换过程。


def obj2dict(obj):
 d = {}
 d['class'] = obj.class.name
 d['module'] = obj.module
 d.update(obj.dict)
 return d

def dict2obj(d):
 if 'class' in d:
  class_name = d.pop('class')
  module_name = d.pop('module')
  module = import(module_name)
  class_ = getattr(module, class_name)
  args = dict((key.encode('ascii'), value) for key, value in d.items())
  instance = class_(**args)
 else:
  instance = d
 return instance

继承JSONEncoder实现反序列化时还有一个额外的作用,就是可以通过iterencode()方法把一个很大的数据对象分多次进行序列化,这对于网络传输、磁盘持久化等情景非常有用。


>>> for chunk in MyJSONEncoder().iterencode(stu):
...  print(chunk)
...
{
"class"
:
"Student"
,
"name"
:
"Tom"
,
"module"
:
"main"
,
"sno"
:
1
,
"age"
:
19
}

大数据对象序列化网络传输伪代码:


for chunk in JSONEncoder().iterencode(bigobject):
 mysocket.write(chunk)

序列化测试:


>>> import json

>>> obj2dict(stu)
{'sno': 1, 'module': 'main', 'age': 19, 'class': 'Student', 'name': 'Tom'}

>>> json.dumps(obj2dict(stu))
'{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}'

>>> json.dumps(stu, default=obj2dict)
'{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}'

json.dumps(stu, default=obj2dict) 等价于 json.dumps(obj2dict(stu))

反序列化测试:


>>> json.loads('{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}')
{u'sno': 1, u'module': u'main', u'age': 19, u'name': u'Tom', u'class': u'Student'}

>>> dict2obj(json.loads('{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}'))
Student [name: Tom, age: 19, sno: 1]

>>> json.loads('{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}', object_hook=dict2obj)
Student [name: Tom, age: 19, sno: 1]

json.loads(JSON_STR, object_hook=dict2obj) 等价于 dict2obj(json.loads(JSON_STR))

方法2:继承JSONEncoder和JSONDecoder实现子类


import json

class MyJSONEncoder(json.JSONEncoder):
 def default(self, obj):
  d = {}
  d['class'] = obj.class.name
  d['module'] = obj.module
  d.update(obj.dict)
  return d

class MyJSONDecoder(json.JSONDecoder):
 def init(self):
  json.JSONDecoder.init(self, object_hook=self.dict2obj)
 
 def dict2obj(self, d):
  if 'class' in d:
   class_name = d.pop('class')
   module_name = d.pop('module')
   module = import(module_name)
   class_ = getattr(module, class_name)
   args = dict((key.encode('ascii'), value) for key, value in d.items())
   instance = class_(**args)
  else:
   instance = d
  return instance

序列化测试:


>>> stu = Student('Tom', 19, 1)

# 方式一:直接调用子类MyJSONEncoder的encode()方法进行序列化
>>> MyJSONEncoder().encode(stu)
'{"class": "Student", "module": "main", "name": "Tom", "age": 19, "sno": 1}'
>>> MyJSONEncoder(separators=(',', ':')).encode(stu)
'{"class":"Student","module":"main","name":"Tom","age":19,"sno":1}'

# 方式二:将子类MyJSONEncoder作为cls参数的值传递给json.dumps()函数
>>> json.dumps(stu, cls=MyJSONEncoder)
'{"class": "Student", "module": "main", "name": "Tom", "age": 19, "sno": 1}'
>>> json.dumps(stu, cls=MyJSONEncoder, separators=(',', ':'))
'{"class":"Student","module":"main","name":"Tom","age":19,"sno":1}'

反序列化测试:


>>> MyJSONDecoder().decode('{"sno": 1, "module": "main", "age": 19, "class": "Student", "name": "Tom"}')
Student [name: Tom, age: 19, sno: 1]

说明: 经过测试发现MyJSONDecoder().decode(JSON_STR) json.loads(JSON_STR, object_hook=dict2obj) 只能在Python 2.7上正确执行,在Python 3.5上无法正确执行;而 json.loads(JSON_STR, cls=MyJSONDecoder) 无论在Python 2.7还是在Python 3.5上都无法正确执行。这说明json模块对于自定义数据类型的反序列化支持还是比较有限的,但是我们也可以通过json.loads(JSON_STR)函数,不指定cls参数来得到一个dict对象,然后自己完成dict到object的转换。

三、pickle模块

pickle模块实现了用于对Python对象结构进行 序列化 和 反序列化 的二进制协议,与json模块不同的是pickle模块序列化和反序列化的过程分别叫做 pickling 和 unpickling:

  1. pickling: 是将Python对象转换为字节流的过程;

  2. unpickling: 是将字节流二进制文件或字节对象转换回Python对象的过程;

1. pickle模块与json模块对比

  1. JSON是一种文本序列化格式(它输出的是unicode文件,大多数时候会被编码为utf-8),而pickle是一个二进制序列化格式;

  2. JOSN是我们可以读懂的数据格式,而pickle是二进制格式,我们无法读懂;

  3. JSON是与特定的编程语言或系统无关的,且它在Python生态系统之外被广泛使用,而pickle使用的数据格式是特定于Python的;

  4. 默认情况下,JSON只能表示Python内建数据类型,对于自定义数据类型需要一些额外的工作来完成;pickle可以直接表示大量的Python数据类型,包括自定数据类型(其中,许多是通过巧妙地使用Python内省功能自动实现的;复杂的情况可以通过实现specific object API来解决)

2. pickle模块使用的数据流格式

上面提到,pickle使用的数据格式是特定于Python的。这使得它不受诸如JSON或XDR的外部标准限值,但是这也意味着非Python程序可能无法重建pickled Python对象。默认情况下,pickle数据格式使用相对紧凑的二进制表示。如果需要最佳大小特征,可以有效的压缩pickled数据。pickletools模块包含可以用于对pickle生成的数据流进行分析的工具。目前有5种不同的协议可以用于pickle。使用的协议越高,就需要更新的Python版本去读取pickle产生的数据:

  1. 协议v0是原始的“人类可读”协议,并且向后倩蓉早期的Python版本;

  2. 协议v1是一个旧的二进制格式,也与早期版本的Python兼容;

  3. 协议v2在Python 2.3中引入,它提供更高效的pickling;

  4. 协议v3是在Python 3.0添加的协议,它明确支持bytes对象,且不能被Python 2.x 进行unpickle操作;这是默认协议,也是当需要兼容其他Python 3版本时被推荐使用的协议;

  5. 协议4是在Python 3.4添加的协议,它添加了对极大对象的支持,pickling更多种类的对象,以及一些数据格式的优化。

说明: Python 2.x中默认使用的是协议v0,如果协议指定为赋值或HIGHEST_PROTOCOL,将使用当前可用的最高协议版本;Python 3.x中默认使用的是协议v3,它兼容其他Python 3版本,但是不兼容Python 2。

注意: 序列化(Serialization)是一个比持久化(Persistence)更加原始的概念;虽然pickle可以读写文件对象,但是它不处理持久化对象的命名问题,也不处理对持久化对象的并发访问问题(甚至更复杂的问题)。pickle模块可以将复杂对象转换为字节流,并且可以将字节流转换为具有相同内部结构的对象。或许最可能对这些字节流做的事情是将它们写入文件,但是也可以对它们进行网络传输或将它们存储在数据库中。shelve模块提供了一个简单的接口用于在DBM风格的数据库文件上对对象进行pickle和unpickle操作。

3. pickle模块提供的相关函数

pickle模块提供的几个序列化/反序列化的函数与json模块基本一致:


# 将指定的Python对象通过pickle序列化作为bytes对象返回,而不是将其写入文件
dumps(obj, protocol=None, *, fix_imports=True)

# 将通过pickle序列化后得到的字节对象进行反序列化,转换为Python对象并返回
loads(bytes_object, *, fix_imports=True, encoding="ASCII", errors="strict")

# 将指定的Python对象通过pickle序列化后写入打开的文件对象中,等价于`Pickler(file, protocol).dump(obj)`
dump(obj, file, protocol=None, *, fix_imports=True)

# 从打开的文件对象中读取pickled对象表现形式并返回通过pickle反序列化后得到的Python对象
load(file, *, fix_imports=True, encoding="ASCII", errors="strict")

说明: 上面这几个方法参数中,*号后面的参数都是Python 3.x新增的,目的是为了兼容Python 2.x,具体用法请参看官方文档。

4. 实例:内置数据类型的序列化/反序列化

Python 2.x


>>> import pickle
>>> 
>>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}

# 序列化
>>> var_b = pickle.dumps(var_a)
>>> var_b
"(dp0\nS'a'\np1\nS'str'\np2\nsS'c'\np3\nI01\nsS'b'\np4\nF11.1\nsS'e'\np5\nI10\nsS'd'\np6\nNsS'g'\np7\n(I4\nI5\nI6\ntp8\nsS'f'\np9\n(lp10\nI1\naI2\naI3\nas."

# 反序列化
>>> var_c = pickle.loads(var_b)
>>> var_c
{'a': 'str', 'c': True, 'b': 11.1, 'e': 10, 'd': None, 'g': (4, 5, 6), 'f': [1, 2, 3]}

Python 3.x


>>> import pickle
>>>
>>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}

# 序列化
>>> var_b = pickle.dumps(var_a)
>>> var_b
b'\x80\x03}q\x00(X\x01\x00\x00\x00eq\x01K\nX\x01\x00\x00\x00aq\x02X\x03\x00\x00\x00strq\x03X\x01\x00\x00\x00fq\x04]q\x05(K\x01K\x02K\x03eX\x01\x00\x00\x00gq\x06K\x04K\x05K\x06\x87q\x07X\x01\x00\x00\x00bq\x08G@&333333X\x01\x00\x00\x00cq\t\x88X\x01\x00\x00\x00dq\nNu.'

# 反序列化
>>> var_c = pickle.loads(var_b)
>>> var_c
{'e': 10, 'a': 'str', 'f': [1, 2, 3], 'g': (4, 5, 6), 'b': 11.1, 'c': True, 'd': None}

dump()与load()


>>> import pickle
>>>
>>> var_a = {'a':'str', 'c': True, 'e': 10, 'b': 11.1, 'd': None, 'f': [1, 2, 3], 'g':(4, 5, 6)}

# 持久化到文件
>>> with open('pickle.txt', 'wb') as f:
...  pickle.dump(var_a, f)
...

# 从文件中读取数据
>>> with open('pickle.txt', 'rb') as f:
...  var_b = pickle.load(f)
...
>>> var_b
{'e': 10, 'a': 'str', 'f': [1, 2, 3], 'g': (4, 5, 6), 'b': 11.1, 'c': True, 'd': None}
>>>

 

说明:

  1. 默认情况下Python 2.x中pickled后的数据是字符串形式,需要将它转换为字节对象才能被Python 3.x中的pickle.loads()反序列化;Python 3.x中pickling所使用的协议是v3,因此需要在调用pickle.dumps()时指定可选参数protocol为Python 2.x所支持的协议版本(0,1,2),否则pickled后的数据不能被被Python 2.x中的pickle.loads()反序列化;

  2. Python 3.x中pickle.dump()和pickle.load()方法中指定的文件对象,必须以二进制模式打开,而Python 2.x中可以以二进制模式打开,也可以以文本模式打开。

5. 实例:自定义数据类型的序列化/反序列化

首先来自定义一个数据类型:


class Student(object):
 def init(self, name, age, sno):
  self.name = name
  self.age = age
  self.sno = sno
 
 def repr(self):
  return 'Student [name: %s, age: %d, sno: %d]' % (self.name, self.age, self.sno)

pickle模块可以直接对自定数据类型进行序列化/反序列化操作,无需编写额外的处理函数或类。


>>> stu = Student('Tom', 19, 1)
>>> print(stu)
Student [name: Tom, age: 19, sno: 1]

# 序列化
>>> var_b = pickle.dumps(stu)
>>> var_b
b'\x80\x03cmain\nStudent\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Tomq\x04X\x03\x00\x00\x00ageq\x05K\x13X\x03\x00\x00\x00snoq\x06K\x01ub.'

# 反序列化
>>> var_c = pickle.loads(var_b)
>>> var_c
Student [name: Tom, age: 19, sno: 1]

# 持久化到文件
>>> with open('pickle.txt', 'wb') as f:
...  pickle.dump(stu, f)
...

# 从文件总读取数据
>>> with open('pickle.txt', 'rb') as f:
...  pickle.load(f)
...
Student [name: Tom, age: 19, sno: 1]

四、shelve模块

shelve是一个简单的数据存储方案,类似key-value数据库,可以很方便的保存python对象,其内部是通过pickle协议来实现数据序列化。shelve只有一个open()函数,这个函数用于打开指定的文件(一个持久的字典),然后返回一个shelf对象。shelf是一种持久的、类似字典的对象。它与“dbm”的不同之处在于,其values值可以是任意基本Python对象--pickle模块可以处理的任何数据。这包括大多数类实例、递归数据类型和包含很多共享子对象的对象。keys还是普通的字符串。


open(filename, flag='c', protocol=None, writeback=False)

flag 参数表示打开数据存储文件的格式,可取值与dbm.open()函数一致:

描述
'r' 以只读模式打开一个已经存在的数据存储文件
'w' 以读写模式打开一个已经存在的数据存储文件
'c' 以读写模式打开一个数据存储文件,如果不存在则创建
'n' 总是创建一个新的、空数据存储文件,并以读写模式打开

 

protocol 参数表示序列化数据所使用的协议版本,默认是pickle v3;

writeback 参数表示是否开启回写功能。

我们可以把shelf对象当dict来使用--存储、更改、查询某个key对应的数据,当操作完成之后,调用shelf对象的close()函数即可。当然,也可以使用上下文管理器(with语句),避免每次都要手动调用close()方法。

实例:内置数据类型操作


# 保存数据
with shelve.open('student') as db:
 db['name'] = 'Tom'
 db['age'] = 19
 db['hobby'] = ['篮球', '看电影', '弹吉他']
 db['other_info'] = {'sno': 1, 'addr': 'xxxx'}

# 读取数据
with shelve.open('student') as db:
 for key,value in db.items():
  print(key, ': ', value)

输出结果:

name :  Tom
age :  19
hobby :  ['篮球', '看电影', '弹吉他']
other_info :  {'sno': 1, 'addr': 'xxxx'}

实例:自定义数据类型操作


# 自定义class
class Student(object):
 def init(self, name, age, sno):
  self.name = name
  self.age = age
  self.sno = sno
 
 def repr(self):
  return 'Student [name: %s, age: %d, sno: %d]' % (self.name, self.age, self.sno)

# 保存数据
tom = Student('Tom', 19, 1)
jerry = Student('Jerry', 17, 2)

with shelve.open("stu.db") as db:
 db['Tom'] = tom
 db['Jerry'] = jerry

# 读取数据
with shelve.open("stu.db") as db:
 print(db['Tom'])
 print(db['Jerry'])

输出结果:
Student [name: Tom, age: 19, sno: 1]
Student [name: Jerry, age: 17, sno: 2]

五、总结

1. 对比

json模块常用于编写web接口,将Python数据转换为通用的json格式传递给其它系统或客户端;也可以用于将Python数据保存到本地文件中,缺点是明文保存,保密性差。另外,如果需要保存费内置数据类型需要编写额外的转换函数或自定义类。

pickle模块和shelve模块由于使用其特有的序列化协议,其序列化之后的数据只能被Python识别,因此只能用于Python系统内部。另外,Python 2.x 和 Python
 3.x 默认使用的序列化协议也不同,如果需要互相兼容需要在序列化时通过protocol参数指定协议版本。除了上面这些缺点外,pickle模块和shelve模块相对于json模块的优点在于对于自定义数据类型可以直接序列化和反序列化,不需要编写额外的转换函数或类。

shelve模块可以看做是pickle模块的升级版,因为shelve使用的就是pickle的序列化协议,但是shelve比pickle提供的操作方式更加简单、方便。shelve模块相对于其它两个模块在将Python数据持久化到本地磁盘时有一个很明显的优点就是,它允许我们可以像操作dict一样操作被序列化的数据,而不必一次性的保存或读取所有数据。

2. 建议

  1. 需要与外部系统交互时用json模块;

  2. 需要将少量、简单Python数据持久化到本地磁盘文件时可以考虑用pickle模块;

  3. 需要将大量Python数据持久化到本地磁盘文件或需要一些简单的类似数据库的增删改查功能时,可以考虑用shelve模块。

3. 附录

要实现的功能 可以使用的api
将Python数据类型转换为(json)字符串 json.dumps()
将json字符串转换为Python数据类型 json.loads()
将Python数据类型以json形式保存到本地磁盘 json.dump()
将本地磁盘文件中的json数据转换为Python数据类型 json.load()
将Python数据类型转换为Python特定的二进制格式 pickle.dumps()
将Python特定的的二进制格式数据转换为Python数据类型 pickle.loads()
将Python数据类型以Python特定的二进制格式保存到本地磁盘 pickle.dump()
将本地磁盘文件中的Python特定的二进制格式数据转换为Python数据类型 pickle.load()
以类型dict的形式将Python数据类型保存到本地磁盘或读取本地磁盘数据并转换为数据类型 shelve.open()

以上がPythonのデータシリアル化(json、pickle、shelve)の詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。