python序列化模块-shelve模块详解

shelve:vt. 将(书等)放置在架子上;搁置,将某事放到一旁不予考虑;将…搁在一边;装搁架于;

个人感觉有点像字典缓存?暂时搁置到一旁的意思?

研究了一段时间后,感觉它就是当成了一种临时的数据库(dbm)缓存文件来用的感觉。

为什么用shelve?

(特别是在已有json和pickle的情况下)

使用json或者pickle持久化数据,能dump多次,但load的话只能取到最新的dump,

因为先前的数据已经被后面dump的数据覆盖掉了。

如果想要实现dump多次不被覆盖,就可以想到使用shelve模块。

shelve模块可以持久化所有pickle所支持的数据类型。

另外,写程序的时候如果不想用关系数据库那么重量级的去存储数据,也可以用到shelve。

shelf也是用key来访问的,使用起来和字典类似。

注意的是,在shelve模块中,key必须为字符串,而值可以是python所支持的数据类型。

另外,shelve其实用anydbm去创建DB并且管理持久化对象的。

shelve只提供给我们一个open方法,是用key来访问的,使用起来和字典类似。

可以像字典一样使用get来获取数据等。

shelve.py中的open方法代码如下:

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

"""Open a persistent dictionary for reading and writing.

# 打开一个持久的字典,用于阅读和写作。

The filename parameter is the base filename for the underlying

database. As a side-effect, an extension may be added to the

filename and more than one file may be created. The optional flag

parameter has the same interpretation as the flag parameter of

dbm.open(). The optional protocol parameter specifies the

version of the pickle protocol (0, 1, or 2).

See the module's __doc__ string for an overview of the interface.

"""

return DbfilenameShelf(filename, flag, protocol, writeback)

源码中的一些有关说明摘录:

To summarize the interface (key is a string, data is an arbitrary

object):

import shelve

d = shelve.open(filename) # open, with (g)dbm filename -- no suffix

d[key] = data # store data at key (overwrites old data if

# using an existing key)

文件句柄[key] = 你想存储的数据 #存储数据在键(如果使用现有的key,将会覆盖旧数据)

data = d[key] # retrieve a COPY of the data at key (raise

# KeyError if no such key) -- NOTE that this

# access returns a *copy* of the entry!

在键上检索数据的副本(如果没有这样的键,就会抛出键错误)——注意,这个访问返回了条目的*拷贝* !

del d[key] # delete data stored at key (raises KeyError

# if no such key)

删除存储在key中的数据(如果没有这样的键,就会出现KeyError)

flag = key in d # true if the key exists 如果键存在,则为真。

list = d.keys() # a list of all existing keys (slow!) 所有现有键的列表(注意:缓慢!)

d.close() # close it 关闭文件句柄

上面的说明我们主要要注重到 d[key] = data 和 data = d[key],这俩充分说明了shelve的一些机制。

首先,shelve做为一个类似数据库缓存的大字典,肯定得支持用户对它写入一些键,这个很好理解。

但是,如果你在shelve db中己存在有一个key,你重新再写入与它同名的key的一些数据(data),

那新写入的这个覆盖掉旧的同样也是很好理解的。

那么不好理解的就是,为什么你对一个key里面的值去作出增加或删除其中一个元素的时候会修改不成功?

这里在data = d[key]里其实就给出了答案,这种属于对key的访问,返回的其实是条目(key)的一个拷贝!

所以才有了writeback这个默认参数的存在,让你可以自主选择要不要做出修改后,将拷贝写回!

这样就可以修改生效。

例:

import shelve

db1 = shelve.open('shelve_db1')

db1['dic'] = {'int':12, 'float':2.5, 'string':'shelve db'}

#直接对文件句柄[key]操作,就可以存入数据

db1.close()

且重要的是它还会直接在打开的当前目录生成三个文件:

shelve.db1.bak

shelve.db1.dat

shelve.db1.dir

其中shelve.db1.dat 存储的就是b字节数据类型的数据,

bak和dir后缀的就可能是和数据库相关的设计缓存之类的东西了。

注:文件生成后,我们可以将前面的这一段生成shelve db的代码先行注释掉,不影响下面的继续测试操作。

db1 = shelve.open('shelve_db1')

existing = db1['dic']

# 取出数据的时候也只需要直接用字典的操作方法获取即可,但是如果key不存在会报错

db1.close()

print(type(existing), existing)

# {'string': 'Shelve db', 'float': 2.5, 'int': 12}

shelve模块有个限制,它不支持多个应用同一时间往同一个DB(文件)进行写操作。

所以如果只需进行读操作,可以修改默认参数flag='r' 让shelve通过只读方式打开DB(文件)。

注:经测试,目前发现的是r模式在python2.7环境下可以生效,

但python3.5不生效,很可能与python3.5不在存在anydbm模块有些关系,

python2.7中是存在anydbm的,而shelve实际上是anydbm的加强版,有可能就是dbm这里产生的问题了。

db1 = shelve.open('shelve_db1', flag='r')

res1 = db1['dic']['int']

db1.close()

print(type(res1), res1)

# 12

由于shelve在默认情况下是不会记录对持久化对象(字典下的键的值-条目)做出修改的,

所以在shelve.open()时候需要修改默认参数writeback=True,

否则对象的条目修改不会'拷贝回写'来进行保存。

import shelve

db1 = shelve.open('shelve_db1', writeback=True)

res2 = db1['dic']['date'] = '2018-4-20'

db1.close()

print(type(res2), res2)

# 2018-4-20

当试图让shelve去自动捕获对象的变化时,应当在打开shelf的时候将writeback设置为True。

而将writeback这个flag设置为True以后,shelf将会将所有从DB中读取的对象存放到一个内存缓存。

当close() shelf的时候,缓存中所有的对象会被重新写入DB。

关于key的数据类型:

import shelve

db2 = shelve.open('shelve_db2.dat')

db2[(1, 2)] = {'lv1':'枪兵'}

print(type(db2[(1, 2)]), db2[(1, 2)])

db2.close()

以上会产生报错:AttributeError: 'tuple' object has no attribute 'encode'

虽然看似shelve db是一个字典,但它的key得支持encode方法,所以

在shelve模块中,key必须为字符串,而值可以是python所支持的数据类型。

例:更详尽的测试说明

import shelve

list1 = ['tie', 'le', 'yu']

# 既然最终生成的文件会是dat格式的,何不一开始就指定后缀是dat

db2 = shelve.open('shelve_db2.dat')

db2['lis'] = list1

# 文件句柄是通过字典的操作方式去拿里面的键值对,lis这个键对应的值是一个列表

db2['lis'].append('mao')

# 而此列表增加一个字符串元素后再打印,感觉不出有发生增加的变化

print(type(db2['lis']), db2['lis'])

# 返回列表:['tie', 'le', 'yu']

没有'mao' ,存储的'mao'到哪里去了呢?

其实很简单,lis 并没有写回,虽然把['tie','le','yu']存到了lis,

但当你再次读取db2['lis']的时候,db2['lis']只是一个拷贝,

而你没有用默认参数writeback将拷贝写回,

当你再次读取db2['lis']的时候,它又从数据源中读取了一个拷贝,

所以,你新修改的内容并不会出现在拷贝中,解决的办法最方便的就是使用默认参数writeback=True,

然后还有一个方法是利用中间缓存的变量,如下所示:

利用中间变量

import shelve

list2 = ['tie', 'le', 'yu']

db2 = shelve.open('shelve_db2.dat')

db2['lis'] = list2

temp = db2['lis']

temp.append('mao')

db2['lis'] = temp # 这种属于直接赋值和拷贝写回无关,会生效

print(type(db2['lis']), db2['lis'])

返回的结果中有  ['tie', 'le', 'yu', 'mao']

直接修改默认参数writeback=True 如下:

import shelve

list3 = ['a', 'b', 'c']

db2 = shelve.open('shelve_db2.dat', writeback=True)

db2['lis2'] = list3

db2['lis2'].append('d')

for k, v in db2.items():

print(k, v)

# 显示从测试开始存在shelve_db2.dat数据文件中键和值如下,可以看到lis2也是成功添加了'd'的。

# lis2 ['a', 'b', 'c', 'd']

# dic ['tie', 'le', 'yu']

# lis ['tie', 'le', 'yu', 'mao']

db2.close()

import shelve

db2 = shelve.open('shelve_db2.dat')

db2['lis2'] = ['1', '2', '3'] # 这是直接赋值,新列表覆盖掉旧列表, 所以并不需要用到回写参数

for k, v in db2.items():

print(k, v)

# 显示如下

# lis ['tie', 'le', 'yu', 'mao']

# lis2 ['1', '2', '3']

# dic ['tie', 'le', 'yu']

db2.close()

同理,以下想对字典进行添加的操作,实际上也是拷贝没有写回,所以看起来没有保存修改成功一样

import shelve

db2 = shelve.open('shelve_db2.dat')

db2['dic2'] = {'name':'铁乐', 'age':18, 'sex':'男'}

db2['dic2']['hobby'] = ['下棋']

# 此时虽然看似添加了一个新键值对,其实并没有做写回操作,

# 下面再做打印操作时,显示的还是从源中取出的一个拷贝,不会有显示增加的键值

print(type(db2['dic2']), db2['dic2'])

db2.close()

# 显示 {'sex': '男', 'age': 18, 'name': '铁乐'}

所以我们一定要弄明白一件事情,

从shelve的db文件中重新再访问一个key拿的是它的拷贝!

修改此拷贝后不做拷贝写回并不影响原来的key,

但你要是直接做的操作是赋值新的值到一个key里,那肯定就是指向原来的key,会被覆盖的。

而这种赋值覆盖对于shelve来说这是一个正常的行为阿。

和键中的值看起来不能被修改一事并不矛盾。

writeback方式有优点也有缺点。

优点是减少了我们出错的概率,且让对象的持久化对用户更加的透明了;

但这种方式并不是所有的情况下都需要,

首先,使用writeback以后,shelf在open()的时候会增加额外的内存消耗,

并且当DB在close()的时候会将缓存中的每一个对象都写入到DB,这也会带来额外的等待时间。

因为shelve没有办法知道缓存中哪些对象修改了,哪些对象没有修改,因此所有的对象都会被写入。

应用场景例子:

模拟保存用户登录状态:

距离上一次登录不超过设置时间内的可以重新登录,

超过时间则无法再使用原用户密码登录。

又需要重新注册。

import time

import datetime

import hashlib

import shelve

# 模拟一个网站登录,新用户先进行注册再登录,

# 旧用户登录判断登录的时间,离上一次登录时间超过多长时间的就再也不能登录了。

# 只适用于一些开放的临时登录的场景?

# 测试时设置登录超时的时间为6分钟,实际应用可以设时间久一点

LOGIN_TIME_OUT = 0.60

# 设置一个临时db,且允许拷贝写回

db = shelve.open('user_shelve.db', writeback=True)

# 新用户登录函数,后面测试后发现其实就是相当于新注册一个用户!

def newuser():

prompt = "login desired: " # prompt,提示

while True:

name = input(prompt).strip()

if name in db:

prompt = "name taken, try another: " # 用户己存在,请重新输入

continue

elif len(name) == 0:

prompt = "name should not be empty, try another: " # 用户名不应该是空的,请重新输入

continue

else:

break

pwd = input("password: ").strip()

db[name] = {"password": md5_digest(pwd), "last_login_time": time.time()}

# 判断用户有没有已存在登录及记录上一次登录时间的函数(现有用户)

def olduser():

name = input("login: ").strip()

pwd = input("password: ").strip()

try:

password = db.get(name).get('password')

# 捕获一个异常,试图访问一个对象没有的属性,也就是处理用户输入不存在的用户时

except AttributeError:

print("\033[1;31;40mUsername '%s' doesn't existed\033[0m" % name)

# 提示用户不存在

return

if md5_digest(pwd) == password:

login_time = time.time() # 当前登录时间

print(login_time)

last_login_time = db.get(name).get('last_login_time') # 上一次登录时间

print(last_login_time)

if login_time - last_login_time < LOGIN_TIME_OUT: # 如果登录没有超时

print("\033[1;31;40mYou already logged in at: \033[0m" % datetime.datetime.fromtimestamp(

last_login_time).isoformat()) # 显示你准备登录的时间

db[name]['last_login_time'] = login_time # 写入登录时间到db

print("\033[1;32;40mwelcome back\033[0m", name) # 显示欢迎回来

else:

print("\033[1;31;40mlogin incorrect\033[0m") # 否则显示登录失败

# md5摘要加密传输进来的明文密码

def md5_digest(plain_pass):

md5 = hashlib.md5()

md5.update(plain_pass.encode('utf-8'))

return md5.hexdigest()

# 菜单

def showmenu():

# 下面菜单分别是新用户登录,当前用户登录,退出

prompt = """

(N)ew User Login

(E)xisting User Login

(Q)uit

Enter choice: """

done = False # 完成 默认值false

while not done:

chosen = False # 选择 默认值false

while not chosen:

try:

choice = input(prompt).strip()[0].lower()

# 捕获异常选择直接变成选q退出程序

except (EOFError, KeyboardInterrupt):

choice = "q"

print("\nYou picked: [%s]" % choice) # 提示你的选择是什么

if choice not in "neq":

print("invalid option, try again") # 当输入的不为neq时,提示输入有误请重新输入

else:

chosen = True # 选择 为真,中断循环

if choice == "q": done = True # 选择为q,退出,中断循环

if choice == "n": newuser() # 选择为n,执行newuser()函数

if choice == "e": olduser() # 选择为e,执行olduser()函数

db.close() # 关闭db文件句柄

# 执行主程序

showmenu()

效果如下:

(N)ew User Login

(E)xisting User Login

(Q)uit

Enter choice: n

# 之前测试己将tiele与mao用户存在临时db文件中

You picked: [n]

login desired: tiele

name taken, try another: mao

# 提示用户己存在,请重新再注册一个新用户

name taken, try another: yue

password: 123

# 新注册了一个yue用户,密码123的在db里

(N)ew User Login

(E)xisting User Login

(Q)uit

Enter choice: e

You picked: [e]

login: tiele

password: 123456

welcome back tiele

# 登录成功

# 后面感觉扩展成不用再输密码,而是直接登录那种可能还贴近一些记住密码的场景?

You picked: [e]

login: le

password: 123

Username 'le' doesn't existed

# 提示用户不存在

You picked: [e]

login: mao

password: 123456

login incorrect # 离上一次登录时间超时,用户登录失效

总之,大致上就是如此,shelve模块它可以当成一个轻量的数据库db来使用,

比起刚学python的时候使用文件来说,还是比较显得有趣和高大上一些的。

利用它的字典特性,也能玩转一下记录用户登录状态来完成一些例如再进其它页面,

再调用其它函数或方法就能正常调用不需重新认证等,简洁代码。

模拟感受一下有数据库存在的情景等还是比json和pickle有用的。

小结:

1、shelve模块将内存数据以字典的类型(key,value)通过文件持久化,模拟出简单的db效果。

2、shelve模块可以持久化任何pickle可支持的python数据格式,但是它的key必需得是字符串。

3、shelve可以看作是pickle模块的一个封装,但它实现了可以多次dump(后面的dump不会覆盖前面的)和多次load。

4、shelve访问己有key时,实际上取出的是数据源给出的一份拷贝,

所以对于拷贝做出的增加和删除等操作都需要用writeback=True参数才能实现写入回源中进行修改。

5、shelve对于d[key] = data这种操作,视为存储数据,无则新增,有则覆盖,

与访问key对当中的值(条目)进行修改默认不回写并不矛盾和冲突。

6、默认安装环境下,shelve中的r只读模式在python2.7能生效,在3.5环境中则不能生效。

有很大可能是与2.7中存在anydbm模块,而3.5中只存在dbm模块而不存在anydbm有关。

end

2018-4-21

乐学python_铁乐学python_shelve模块详解相关推荐

  1. 乐学python_铁乐学python_day01-作业

    第一题:使用while循环输入 1 2 3 4 5 6 8 9 10 # 使用while循环输入 1 2 3 4 5 6 8 9 10 count = 0 while (True) : count = ...

  2. 乐学python_铁乐学Python_day09_函数

    今天我们来学习函数. 产生的原由,若没有函数,会显得重复代码多,可读性差,也会造成重复造轮子的情景. 故产生了函数,用来封装好一个功能,它是以功能为导向的. 1.[函数的样式] 例如自己定义一个函数: ...

  3. 铁乐学python-shelve模块详解

    python序列化模块-shelve模块详解 shelve:vt. 将(书等)放置在架子上:搁置,将某事放到一旁不予考虑:将-搁在一边:装搁架于: 个人感觉有点像字典缓存?暂时搁置到一旁的意思? 研究 ...

  4. 智慧成铁显示无法连接服务器是怎么回事,智慧成铁是什么软件?智慧成铁职工app功能详解[图]...

    类型:商务办公 大小:40.29MB 评分:10 平台: 智慧成铁是一款不错的成都铁路职工工作软件!目前安卓版已经上线了,小伙伴们可以点击文章上方的立即下载,不少朋友对于智慧成铁app还不太了解,赶紧 ...

  5. 【ES6】Module模块详解

    [ES6]Module模块详解 一.Module的由来 二.严格模式 三.export命令 四.import命令 查看更多ES6教学文章: 参考文献 引言:由于两个JS文件之间相互使用必须通过一个ht ...

  6. python常用内置模块-Python常用内置模块之xml模块(详解)

    xml即可扩展标记语言,它可以用来标记数据.定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言.从结构上,很像HTML超文本标记语言.但他们被设计的目的是不同的,超文本标记语言被设计用来显示 ...

  7. python之sys模块详解_(转)python之os,sys模块详解

    python之sys模块详解 原文:http://www.cnblogs.com/cherishry/p/5725184.html sys模块功能多,我们这里介绍一些比较实用的功能,相信你会喜欢的,和 ...

  8. Ansible基本使用及常用模块详解

    一.ansible基本使用 定义主机组 定义被管理节点列表的文件/etc/ansible/hosts,在定义被管理节点的时候,可以单独基于主机做定义,也可以将多个主机定义成一个主机组. 在上篇博文安装 ...

  9. Nginx源码研究之nginx限流模块详解

    这篇文章主要介绍了Nginx源码研究之nginx限流模块详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 高并发系统有三把利器:缓存.降级和限流: 限流的目的是通过对并 ...

最新文章

  1. 一个Demo让你掌握所有的android控件
  2. 利用CMake编译内核模块
  3. boost::geometry::detail::get_turns用法的测试程序
  4. linux下FTP服务搭建(1)
  5. oracle查看所有用户_Oracle实用命令查看共用一个表空间的所有用户
  6. 英特尔强势上新一大波数据产品,小伙伴们“奔走相告”…… | 极客头条
  7. python post有随机字符串_Python 中的POST/GET包构建以及随机字符串的生成
  8. DB2造数据存储过程
  9. POCO库中文编程参考指南(11)如何使用Reactor框架?
  10. 分享:架构师软技能之协商
  11. ADO.NET Entity Framework(3)ObjectContext
  12. 在程序中表示什么_程序开发中:什么是前后端分离?你搞清楚了吗?
  13. 北京各区优质高中排名
  14. 使用python爬取号码归属地列表,制作握手包破解字典
  15. 什么是EasyUI,如何使用EasyUI?--easyui的十二种用法
  16. 【Selenium自动化测试】鼠标与键盘操作
  17. python制作简单动画_让数据动起来!用python制作动画可视化效果,让数据不再枯燥!...
  18. iOS开发之支付宝集成
  19. 如何快速体验腾讯云区块链长安链
  20. 小米拒绝权限_小米应用商城:我下载的是快图,打开却是天天相册,还发扣费短信...

热门文章

  1. 超级详细!java实现阿里云视频直播功能
  2. 零点工作室暑假集训(AtCoder--ABC279)
  3. linux文件的三种时间,修改时间、状态时间和读取时间
  4. Win7不时出现“此Windows副本不是正版”提示解决方法
  5. realme真我X7和X7Pro的区别 哪个好
  6. linux文件打开命令
  7. python后端需要什么基础_一个六年经验的python后端是怎么学习用java写API的(6) 基本的Auth...
  8. 人工智能芯片龙头之一gti概念股_人工智能概念股有哪些 人工智能芯片谁是龙头?...
  9. python消除C语言注释
  10. Linux常见指令与shell理解