python 序列化 pickle shelve json configparser

1. 什么是序列化

 我们把变量从内存中变成可存储或传输的过程称之为序列化。

 序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

 反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

 举例:大家应该都玩过魔兽争霸,应该知道该游戏有一个存档的功能,我每次不想玩得时候就可以存档,然后再玩得时候我们根本不需要重新开始玩,只需要读档就可以了。我们现在学习的事面向对象的思想,那么在我们眼中不管是我们的游戏角色还是游戏中的怪物、装备等等都可以看成是 一个个的对象,进行简单的分析。

角色对象(包含等级、性别、经验值、HP、MP等等属性) 
武器对象(包含武器的类型、武器的伤害、武器附加的能力值等等属性) 
怪物对象(包含等级、经验值、攻击、怪物类型等等) 
于是玩游戏过程变的非常有意思了,创建游戏角色就好像是创建了一个角色对象,拿到武器就好像创建了一个武器对象,遇到的怪物、NPC等等都是对象了。 
然后再用学 过的知识进行分析,我们发现对象的数据都是保存在内存中的,应该都知道内存的数据在断电以后是会消失的,但是我们的游戏经过存档以后,就算你关机了几天, 再进入游戏的时候,读取你的存档发现你在游戏中的一切都还在呢,奇怪了,明明内存中的数据已经没有了啊,这是为什么呢?于是再仔细考虑,电脑中有硬盘这个 东西在断电以后保存的数据是不会丢的(要是由于断电导致的硬盘损坏了,没有数据了,哈哈,不在此考虑中)。那么应该很容易的想到这些数据是被保存在硬盘中 了。没错!这就是对象的持久化,也就是我们今天要讲的对象的序列化。那么反序列化就很好理解了就是将存放在硬盘中的信息再读取出来形成对象。

 

---如何序列化?

  在python中提供了两个模块可进行序列化。分别是pickle和json。


2. pickle(重点)

用于序列化的两个模块
  json:用于字符串和Python数据类型间进行转换
  pickle: 用于python特有的类型和python的数据类型间进行转换
  json提供四个功能:dumps,dump,loads,load
  pickle提供四个功能:dumps,dump,loads,load

pickle可以存储什么类型的数据呢?

  1. 所有python支持的原生类型:布尔值,整数,浮点数,复数,字符串,字节,None。
  2. 由任何原生类型组成的列表,元组,字典和集合。
  3. 函数,类,类的实例

pickle模块中常用的方法有:

    1. pickle.dump(obj, file, protocol=None,)

    必填参数obj表示将要封装的对象

    必填参数file表示obj要写入的文件对象,file必须以二进制可写模式打开,即“wb”

    可选参数protocol表示告知pickler使用的协议,支持的协议有0,1,2,3,默认的协议是添加在Python 3中的协议3。   

  • Protocol version 0 is the original “human-readable” protocol and is backwards compatible with earlier versions of Python.
  • Protocol version 1 is an old binary format which is also compatible with earlier versions of Python.
  • Protocol version 2 was introduced in Python 2.3. It provides much more efficient pickling of new-style classes. Refer to PEP 307 for information about improvements brought by protocol 2.
  • Protocol version 3 was added in Python 3.0. It has explicit support for bytes objects and cannot be unpickled by Python 2.x. This is the default protocol, and the recommended protocol when compatibility with other Python 3 versions is required.
  • Protocol version 4 was added in Python 3.4. It adds support for very large objects, pickling more kinds of objects, and some data format optimizations. Refer to PEP 3154 for information about improvements brought by protocol 4. 

    2. pickle.load(file,*,fix_imports=True, encoding="ASCII", errors="strict")

    必填参数file必须以二进制可读模式打开,即“rb”,其他都为可选参数

    3. pickle.dumps(obj):以字节对象形式返回封装的对象,不需要写入文件中

    4. pickle.loads(bytes_object): 从字节对象中读取被封装的对象,并返回

 pickle模块可能出现三种异常:

    1. PickleError:封装和拆封时出现的异常类,继承自Exception

    2. PicklingError: 遇到不可封装的对象时出现的异常,继承自PickleError

    3. UnPicklingError: 拆封对象过程中出现的异常,继承自PickleError

应用:

复制代码
复制代码
1 # dumps功能
2 import pickle
3 data = ['aa', 'bb', 'cc']  
4 # dumps 将数据通过特殊的形式转换为只有python语言认识的字符串
5 p_str = pickle.dumps(data)
6 print(p_str)            
7 b'\x80\x03]q\x00(X\x02\x00\x00\x00aaq\x01X\x02\x00\x00\x00bbq\x02X\x02\x00\x00\x00ccq\x03e.
复制代码
复制代码
1 # loads功能
2 # loads  将pickle数据转换为python的数据结构
3 mes = pickle.loads(p_str)
4 print(mes)
5 ['aa', 'bb', 'cc']
1 # dump功能
2 # dump 将数据通过特殊的形式转换为只有python语言认识的字符串,并写入文件
3 with open('D:/tmp.pk', 'w') as f:
4     pickle.dump(data, f)
1 # load功能
2 # load 从数据文件中读取数据,并转换为python的数据结构
3 with open('D:/tmp.pk', 'r') as f:
4     data = pickle.load(f)


3. shelve

这几天接触了Python中的shelve这个module,感觉比pickle用起来更简单一些,它也是一个用来持久化Python对象的简单工具。当我们写程序的时候如果不想用关系数据库那么重量级的东东去存储数据,不妨可以试试用shelve。shelf也是用key来访问的,使用起来和字典类似。shelve其实用anydbm去创建DB并且管理持久化对象的。

 

创建一个新的shelf

直接使用shelve.open()就可以创建了

复制代码
1 import shelve
2 
3 s = shelve.open('test_shelf.db')
4 try:
5     s['key1'] = { 'int': 10, 'float':9.5, 'string':'Sample data' }
6 finally:
7     s.close()
复制代码

 

如果想要再次访问这个shelf,只需要再次shelve.open()就可以了,然后我们可以像使用字典一样来使用这个shelf

复制代码
1 import shelve
2 
3 s = shelve.open('test_shelf.db')
4 try:
5     existing = s['key1']
6 finally:
7     s.close()
8 
9 print existing
复制代码

 

当我们运行以上两个py,我们将得到如下输出:

$ python shelve_create.py
$ python shelve_existing.py

{'int': 10, 'float': 9.5, 'string': 'Sample data'}

 

dbm这个模块有个限制,它不支持多个应用同一时间往同一个DB进行写操作。所以当我们知道我们的应用如果只进行读操作,我们可以让shelve通过只读方式打开DB:

复制代码
1 import shelve
2 
3 s = shelve.open('test_shelf.db', flag='r')
4 try:
5     existing = s['key1']
6 finally:
7     s.close()
8 
9 print existing
复制代码

当我们的程序试图去修改一个以只读方式打开的DB时,将会抛一个访问错误的异常。异常的具体类型取决于anydbm这个模块在创建DB时所选用的DB。

 

写回(Write-back)

由于shelve在默认情况下是不会记录待持久化对象的任何修改的,所以我们在shelve.open()时候需要修改默认参数,否则对象的修改不会保存。

复制代码
 1 import shelve
 2 
 3 s = shelve.open('test_shelf.db')
 4 try:
 5     print s['key1']
 6     s['key1']['new_value'] = 'this was not here before'
 7 finally:
 8     s.close()
 9 
10 s = shelve.open('test_shelf.db', writeback=True)
11 try:
12     print s['key1']
13 finally:
14     s.close()
复制代码

上面这个例子中,由于一开始我们使用了缺省参数shelve.open()了,因此第6行修改的值即使我们s.close()也不会被保存。

执行结果如下:

$ python shelve_create.py
$ python shelve_withoutwriteback.py

{'int': 10, 'float': 9.5, 'string': 'Sample data'}
{'int': 10, 'float': 9.5, 'string': 'Sample data'}

 

所以当我们试图让shelve去自动捕获对象的变化,我们应该在打开shelf的时候将writeback设置为True。当我们将writeback这个flag设置为True以后,shelf将会将所有从DB中读取的对象存放到一个内存缓存。当我们close()打开的shelf的时候,缓存中所有的对象会被重新写入DB。

复制代码
 1 import shelve
 2 
 3 s = shelve.open('test_shelf.db', writeback=True)
 4 try:
 5     print s['key1']
 6     s['key1']['new_value'] = 'this was not here before'
 7     print s['key1']
 8 finally:
 9     s.close()
10 
11 s = shelve.open('test_shelf.db', writeback=True)
12 try:
13     print s['key1']
14 finally:
15     s.close()
复制代码

writeback方式有优点也有缺点。优点是减少了我们出错的概率,并且让对象的持久化对用户更加的透明了;但这种方式并不是所有的情况下都需要,首先,使用writeback以后,shelf在open()的时候会增加额外的内存消耗,并且当DB在close()的时候会将缓存中的每一个对象都写入到DB,这也会带来额外的等待时间。因为shelve没有办法知道缓存中哪些对象修改了,哪些对象没有修改,因此所有的对象都会被写入。

复制代码
1 $ python shelve_create.py
2 $ python shelve_writeback.py
3 
4 {'int': 10, 'float': 9.5, 'string': 'Sample data'}
5 {'int': 10, 'new_value': 'this was not here before', 'float': 9.5, 'string': 'Sample data'}
6 {'int': 10, 'new_value': 'this was not here before', 'float': 9.5, 'string': 'Sample data'}
复制代码

 

最后再来个复杂一点的例子:

复制代码
 1 #!/bin/env python
 2 
 3 import time
 4 import datetime
 5 import md5
 6 import shelve
 7 
 8 LOGIN_TIME_OUT = 60
 9 db = shelve.open('user_shelve.db', writeback=True)
10 
11 def newuser():
12     global db
13     prompt = "login desired: "
14     while True:
15         name = raw_input(prompt)
16         if name in db:
17             prompt = "name taken, try another: "
18             continue
19         elif len(name) == 0:
20             prompt = "name should not be empty, try another: "
21             continue
22         else:
23             break
24     pwd = raw_input("password: ")
25     db[name] = {"password": md5_digest(pwd), "last_login_time": time.time()}
26     #print '-->', db
27 
28 def olduser():
29     global db
30     name = raw_input("login: ")
31     pwd = raw_input("password: ")
32     try:
33         password = db.get(name).get('password')
34     except AttributeError, e:
35         print "\033[1;31;40mUsername '%s' doesn't existed\033[0m" % name
36         return
37     if md5_digest(pwd) == password:
38         login_time = time.time()
39         last_login_time = db.get(name).get('last_login_time')
40         if login_time - last_login_time < LOGIN_TIME_OUT:
41             print "\033[1;31;40mYou already logged in at: <%s>\033[0m" % datetime.datetime.fromtimestamp(last_login_time).isoformat()
42 
43         db[name]['last_login_time'] = login_time
44         print "\033[1;32;40mwelcome back\033[0m", name
45     else:
46         print "\033[1;31;40mlogin incorrect\033[0m"
47 
48 def md5_digest(plain_pass):
49    return md5.new(plain_pass).hexdigest()
50 
51 def showmenu():
52     #print '>>>', db
53     global db
54     prompt = """
55 (N)ew User Login
56 (E)xisting User Login
57 (Q)uit
58 Enter choice: """
59     done = False
60     while not done:
61         chosen = False
62         while not chosen:
63             try:
64                 choice = raw_input(prompt).strip()[0].lower()
65             except (EOFError, KeyboardInterrupt):
66                 choice = "q"
67             print "\nYou picked: [%s]" % choice
68             if choice not in "neq":
69                 print "invalid option, try again"
70             else:
71                 chosen = True
72 
73         if choice == "q": done = True
74         if choice == "n": newuser()
75         if choice == "e": olduser()
76     db.close()
77 
78 if __name__ == "__main__":
79     showmenu()
复制代码


4. json(重点)

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。JSON的数据格式其实就是python里面的字典格式,里面可以包含方括号括起来的数组,也就是python里面的列表。

在python中,有专门处理json格式的模块—— json 和 picle模块

  Json   模块提供了四个方法: dumps、dump、loads、load

pickle 模块也提供了四个功能:dumps、dump、loads、load
 
一. dumps 和 dump:
 dumps和dump   序列化方法
       dumps只完成了序列化为str,
       dump必须传文件描述符,将序列化的str保存到文件中
 
查看源码:
def 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):
    # Serialize ``obj`` to a JSON formatted ``str``.
    # 序列号 “obj” 数据类型 转换为 JSON格式的字符串 
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
        allow_nan=True, cls=None, indent=None, separators=None,
        default=None, sort_keys=False, **kw):
    """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
    ``.write()``-supporting file-like object).
     我理解为两个动作,一个动作是将”obj“转换为JSON格式的字符串,还有一个动作是将字符串写入到文件中,也就是说文件描述符fp是必须要的参数 """

 

示例代码:

复制代码
>>> import json
>>> json.dumps([])    # dumps可以格式化所有的基本数据类型为字符串
'[]'
>>> json.dumps(1)    # 数字
'1'
>>> json.dumps('1')   # 字符串
'"1"'
>>> dict = {"name":"Tom", "age":23}  
>>> json.dumps(dict)     # 字典
'{"name": "Tom", "age": 23}'
复制代码
a = {"name":"Tom", "age":23}
with open("test.json", "w", encoding='utf-8') as f:
    # indent 超级好用,格式化保存字典,默认为None,小于0为零个空格
    f.write(json.dumps(a, indent=4))
    # json.dump(a,f,indent=4)   # 和上面的效果一样

保存的文件效果:

 

二. loads 和 load 

loads和load  反序列化方法

       loads 只完成了反序列化,
       load 只接收文件描述符,完成了读取文件和反序列化

 

 查看源码:

def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
    """Deserialize ``s`` (a ``str`` instance containing a JSON document) to a Python object.
       将包含str类型的JSON文档反序列化为一个python对象"""
def load(fp, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
    """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing a JSON document) to a Python object.
        将一个包含JSON格式数据的可读文件饭序列化为一个python对象"""

 

实例:

>>> json.loads('{"name":"Tom", "age":23}')
{'age': 23, 'name': 'Tom'}
复制代码
import json
with open("test.json", "r", encoding='utf-8') as f:
    aa = json.loads(f.read())
    f.seek(0)
    bb = json.load(f)    # 与 json.loads(f.read())
print(aa)
print(bb)

# 输出:
{'name': 'Tom', 'age': 23}
{'name': 'Tom', 'age': 23}
复制代码

三. json 和 picle 模块

 json模块和picle模块都有  dumps、dump、loads、load四种方法,而且用法一样。

不用的是json模块序列化出来的是通用格式,其它编程语言都认识,就是普通的字符串,

而picle模块序列化出来的只有python可以认识,其他编程语言不认识的,表现为乱码

不过picle可以序列化函数,但是其他文件想用该函数,在该文件中需要有该文件的定义(定义和参数必须相同,内容可以不同)

四. python对象(obj) 与json对象的对应关系

复制代码
    +-------------------+---------------+
    | Python            | JSON          |
    +===================+===============+
    | dict              | object        |
    +-------------------+---------------+
    | list, tuple       | array         |
    +-------------------+---------------+
    | str               | string        |
    +-------------------+---------------+
    | int, float        | number        |
    +-------------------+---------------+
    | True              | true          |
    +-------------------+---------------+
    | False             | false         |
    +-------------------+---------------+
    | None              | null          |
    +-------------------+---------------+
复制代码

 

 五. 总结

 1. json序列化方法:

          dumps:无文件操作            dump:序列化+写入文件

  2. json反序列化方法:

          loads:无文件操作              load: 读文件+反序列化

  3. json模块序列化的数据 更通用

      picle模块序列化的数据 仅python可用,但功能强大,可以序列号函数

  4. json模块可以序列化和反序列化的  数据类型 见  python对象(obj) 与json对象的对应关系表

  5. 格式化写入文件利用  indent = 4 


5. configparser模块

 

一、ConfigParser简介

 

ConfigParser 是用来读取配置文件的包。配置文件的格式如下:中括号“[ ]”内包含的为section。section 下面为类似于key-value 的配置内容。

 

复制代码
[db]
db_host = 127.0.0.1
db_port = 69
db_user = root
db_pass = root
host_port = 69

[concurrent]
thread = 10
processor = 20
复制代码

 

括号“[ ]”内包含的为section。紧接着section 为类似于key-value 的options 的配置内容。

 

二、ConfigParser 初始化对象

 

使用ConfigParser 首选需要初始化实例,并读取配置文件:

 

import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")

 

三、ConfigParser 常用方法

 

1、获取所用的section节点

 

复制代码
# 获取所用的section节点
import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")
print(config.sections())
#运行结果
# ['db', 'concurrent']
复制代码

 

2、获取指定section 的options。即将配置文件某个section 内key 读取到列表中:

 

复制代码
import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")
r = config.options("db")
print(r)
#运行结果
# ['db_host', 'db_port', 'db_user', 'db_pass', 'host_port']
复制代码

 

3、获取指点section下指点option的值

 

复制代码
import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")
r = config.get("db", "db_host")
# r1 = config.getint("db", "k1") #将获取到值转换为int型
# r2 = config.getboolean("db", "k2" ) #将获取到值转换为bool型
# r3 = config.getfloat("db", "k3" ) #将获取到值转换为浮点型
print(r)
#运行结果
# 127.0.0.1
复制代码

 

4、获取指点section的所用配置信息

 

复制代码
import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")
r = config.items("db")
print(r)
#运行结果
#[('db_host', '127.0.0.1'), ('db_port', '69'), ('db_user', 'root'), ('db_pass', 'root'), ('host_port', '69')]
复制代码

 

5、修改某个option的值,如果不存在则会出创建

 

复制代码
# 修改某个option的值,如果不存在该option 则会创建
import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")
config.set("db", "db_port", "69")  #修改db_port的值为69
config.write(open("ini", "w"))
复制代码

 

复制代码
[db]
db_host = 127.0.0.1
db_port = 69
db_user = root
db_pass = root

[concurrent]
thread = 10
processor = 20
复制代码

 

6、检查section或option是否存在,bool值

 

import configparser
config = configparser.ConfigParser()
config.has_section("section") #是否存在该section
config.has_option("section", "option")  #是否存在该option

 

7、添加section 和 option

 

复制代码
import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")
if not config.has_section("default"):  # 检查是否存在section
    config.add_section("default")
if not config.has_option("default", "db_host"):  # 检查是否存在该option
    config.set("default", "db_host", "1.1.1.1")
config.write(open("ini", "w"))
复制代码

 

复制代码
[db]
db_host = 127.0.0.1
db_port = 69
db_user = root
db_pass = root

[concurrent]
thread = 10
processor = 20

[default]
db_host = 1.1.1.1
复制代码

 

8、删除section 和 option

 

import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")
config.remove_section("default") #整个section下的所有内容都将删除
config.write(open("ini", "w"))

 

复制代码
[db]
db_host = 127.0.0.1
db_port = 69
db_user = root
db_pass = root

[concurrent]
thread = 10
processor = 20
复制代码

 

9、写入文件

 

以下的几行代码只是将文件内容读取到内存中,进过一系列操作之后必须写回文件,才能生效。

 

import configparser
config = configparser.ConfigParser()
config.read("ini", encoding="utf-8")

 

写回文件的方式如下:(使用configparser的write方法)

 

config.write(open("ini", "w"))

 

posted @ 2018-10-10 15:21  Big_C  阅读(362)  评论(0编辑  收藏  举报