铁乐学python_shelve模块详解
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)
# <class 'dict'> {'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)
# <class 'int'> 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)
# <class 'str'> 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'])
返回的结果中有 <class 'list'> ['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()
# 显示<class 'dict'> {'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: <%s>\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_shelve模块详解的更多相关文章
- Python中操作mysql的pymysql模块详解
Python中操作mysql的pymysql模块详解 前言 pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb几乎相同.但目前pymysql支持python3.x而后者不支持 ...
- python之OS模块详解
python之OS模块详解 ^_^,步入第二个模块世界----->OS 常见函数列表 os.sep:取代操作系统特定的路径分隔符 os.name:指示你正在使用的工作平台.比如对于Windows ...
- python之sys模块详解
python之sys模块详解 sys模块功能多,我们这里介绍一些比较实用的功能,相信你会喜欢的,和我一起走进python的模块吧! sys模块的常见函数列表 sys.argv: 实现从程序外部向程序传 ...
- python中threading模块详解(一)
python中threading模块详解(一) 来源 http://blog.chinaunix.net/uid-27571599-id-3484048.html threading提供了一个比thr ...
- python time 模块详解
Python中time模块详解 发表于2011年5月5日 12:58 a.m. 位于分类我爱Python 在平常的代码中,我们常常需要与时间打交道.在Python中,与时间处理有关的模块就包括: ...
- python time模块详解
python time模块详解 转自:http://blog.csdn.net/kiki113/article/details/4033017 python 的内嵌time模板翻译及说明 一.简介 ...
- 小白的Python之路 day5 time,datatime模块详解
一.模块的分类 可以分成三大类: 1.标准库 2.开源模块 3.自定义模块 二.标准库模块详解 1.time与datetime 在Python中,通常有这几种方式来表示时间:1)时间戳 2)格式化的时 ...
- 小白的Python之路 day5 random模块和string模块详解
random模块详解 一.概述 首先我们看到这个单词是随机的意思,他在python中的主要用于一些随机数,或者需要写一些随机数的代码,下面我们就来整理他的一些用法 二.常用方法 1. random.r ...
- Python中time模块详解
Python中time模块详解 在平常的代码中,我们常常需要与时间打交道.在Python中,与时间处理有关的模块就包括:time,datetime以及calendar.这篇文章,主要讲解time模块. ...
随机推荐
- redis实战笔记(10)-第10章 扩展Redis
本章主要内容 扩展读性能 扩展写性能以及内存容量 扩展复杂的查询 随着Redis的使用越来越多, 只使用一台Redis服务器没办法存储所有数据或者没办法处理所有读写请求的问题迟早都会出现, 这 ...
- Spring中使用两种Aware接口自定义获取bean
在使用spring编程时,常常会遇到想根据bean的名称来获取相应的bean对象,这时候,就可以通过实现BeanFactoryAware来满足需求,代码很简单: @Servicepublic clas ...
- 小白学习之Code First(三)
上下文Context类中的base构造器的几个方法重置(1.无参 2.database name 3 . 连接字符串) 无参:如果基类base方法中无参,code first将会以 :{Namespa ...
- 解决 Jquery UI Tooltip 用在Select 的BUG
今天遇到一个奇葩问题, 在chrome下一切正常的 页面,在IE 下Select无法选中! 是的 ,就是无法选中!!! 效果就是这样的.http://jsfiddle.net/slolife/dp4 ...
- 【解决】 无法打开包括文件:“windows.h”: No such file or directory
vs编译时错误: 无法打开包括文件:“windows.h”: No such file or directory 出现这种错误什么都不用配置(环境变量),最好办法是将VS安装在C盘,让开发工具自动包含 ...
- IIS负载均衡-Application Request Route详解第一篇: ARR介绍
IIS负载均衡-Application Request Route详解第一篇: ARR介绍 说到负载均衡,相信大家已经不再陌生了,本系列主要介绍在IIS中可以采用的负载均衡的软件:微软的Applica ...
- 使用DOM创建xml文件
使用DOM创建xml文件 创建xml的代码如下: public class CreateXML { public static void main(String[] args) { DocumentB ...
- PHP生成缩略图(1)--简单缩略图
原理:就是将大图缩小并另存为小图 以此图为例,使其生成缩略图! 首先要使用到以下函数 imagecopyresampled() 重采样拷贝部分图像并调整大小 bool imagecopyresampl ...
- PHPCMS V9标签循环嵌套调用数据的方法
PHPCMS V9的标签制作以灵活见长,可以自由DIY出个性的数据调用,对于制作有风格有创意的网站模板很好用,今天就介绍一个标签循环嵌套方法,可以实现对PC标签循环调用,代码如下: 在此文件里/php ...
- github for window 中 git shell 设置代理方法和解决ssl证书错误的问题
体验了一下传说中的 github for windows(操作git有很多的方法,我还没有学会,所以找了个简单的方法),听说用起来还不错,毕竟也开始接触了github.下载地址是 http://win ...