铁乐学python_day25_序列化模块

部份内容摘自博客http://www.cnblogs.com/Eva-J/

回顾内置方法:
__len__ len(obj)的结果依赖于obj.__len__()的结果,计算对象的长度
__hash__ hash(obj)的结果依赖于obj.__hash__()的结果,计算对象的hash值
__eq__  obj1 == obj2 的结果依赖于obj.__eq__()的结果,用来判断值相等
__str__ str(obj) print(obj) ‘%s‘%obj 的结果依赖于__str__,用来做输出、显示
__repr__ repr(obj) ‘%r‘%obj的结果依赖于__repr__,还可以做str的备胎
__format__ format() 的结果依赖于__format__的结果,是对象格式化的
__call__ obj()相当于调用__call__,实现了__call__的对象是callable的
__new__  构造方法,在执行__init__之前执行,负责创建一个对象,在单例模式中有具体的应用
__del__  析构方法,在对象删除的时候,删除这个对象之前执行,主要用来关闭在对象中打开的系统的资源
item系列:对象[‘值‘]触发
__getitem__ 对象[]的形式对对象进行增删改查
__setitem__
__delitem__
__delattr__ del obj.attr 用来自定义删除一个属性 一般不需要重写它

解析一道面试题
写一个类,定义100个对象,对象都拥有三个属性:name, age, sex。
如果两个对象的name和sex完全相同,我们就认为这是一个对象。
忽略age属性,做这100个对象的去重工作。

class Person:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def __hash__(self):
        # hash算法本身就存在了 且直接在python中就能调用
        # 姓名相同 性别相同的对象的hash值应该相等才行
        # 姓名性别都是字符串
        return hash(self.name+self.sex)
    def __eq__(self, other):
        if self.name == other.name and self.sex == other.sex:
            return True

思路:
python2.7经典类中没有hash方法,直接对类中对象进行set去重会报错,# set([obj,obj])   unhashable
哈希算法和数据可不可变类型没有任何关系,因为hash是一种算法。
可变类型也可以进行哈希,但在一次程序中它会变化,
所以进行hash得出的值也会变化,字典中就无法将其做为key。

# key hash 数字 --》 内存地址 --》 value
# set hash 数字 --》 内存地址 --》 set中的元素

hash算法:
一个值进行一系列的计算得出一个数字在一次程序执行中总是不变,
每一个不同的值计算出的数字都不相等。

set去重:
set对一个对象序列的去重 依赖于这个对象的两个方法:__hash__, __eq__。
如何判断这两个值是否相等,使用hash计算哈希值后再判断有相同哈希值的元素值是否相同会非常高效。

验证:

obj_lst = []
obj_lst.append(Person(‘alex‘,80,‘male‘))
obj_lst.append(Person(‘alex‘,70,‘male‘))
obj_lst.append(Person(‘alex‘,60,‘male‘))
obj_lst.append(Person(‘boss_jin‘,50,‘male‘))
obj_lst.append(Person(‘boss_jin‘,40,‘male‘))
obj_lst.append(Person(‘boss_jin‘,30,‘male‘))
obj_lst.append(Person(‘nezha‘,20,‘male‘))
obj_lst.append(Person(‘nezha‘,10,‘male‘))
obj_lst = set(obj_lst)
for obj in obj_lst:print(obj.name)

运行结果为8个实例对象去重后变成三个:nezha boss_jin alex

进入正题:开始学习模块

Py模块本质来讲它就是py文件。

python之所以好用,有一个很重要的原因是它提供的模块多。

其中模块大致分为三种:

1)内置模块

python安装的时候自带的。

2)扩展模块

例:itchat(微信)beautiful soap (爬虫模块)

selenium (网页自动化测试工具)

diango tornado(框架)

别人写好的(一堆模块),需要的时候安装上之后可以直接使用。

www.pypi.org网站就有提供模块下载。

3)自定义模块

自己写的模块之类

内置模块---之 序列化模块

能存储在文件中一定是字符串或字节,能在网络上传输的只有字节,而字节能转换成字符串。

什么叫序列化——将原本的字典、列表等内容转换成一个字符串的过程就叫做序列化。

比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?

现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来。

但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中。

你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?

没错序列化的过程就是从dic 变成str(dic)的过程。现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串,

但是你要怎么把一个字符串转换成字典呢?

聪明的你肯定想到了eval(),如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。

eval()函数十分强大,但是eval是做什么的?e官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果。

BUT!强大的函数有代价。安全性是其最大的缺点。

想象一下,如果我们从文件中读出的不是一个数据结构,而是一句"删除文件"类似的破坏性语句,那么后果实在不堪设设想。

而使用eval就要担这个风险。

所以,我们并不推荐用eval方法来进行反序列化操作(将str转换成python中的数据结构)

序列化的目的

1、以某种存储形式使自定义对象持久化;

2、将对象从一个地方传递到另一个地方。

3、使程序更具维护性。

序列化 ==> 创造一个序列 ==> 创造一个字符串

反序列化 ==> 字符串 --> 各种数据结构

python 中的序列化模块有以下三种:

json 所有的编程语言都通用的序列化格式(使用广)但它支持的数据类型非常有限(支持窄)

支持的数据类型:数字,字符串,序列(列表,元祖),字典。

pickle 只能在python语言的程序之间传递数据用(使用窄)但pickle支持python中所有的数据类型(支持广)

shelve python3.x 之后才有。

JSON(JavaScript Object Notation)

是一种轻量级的数据交换格式。

它基于ECMAScript的一个子集。

JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯

(包括C、C++、Java、JavaScript、Perl、Python等)。

这些特性使JSON成为理想的数据交换语言。

易于人阅读和编写,同时也易于机器解析和生成(一般用于提升网络传输速率)。

json序列化模块

import json
dic = {‘大表哥‘:(190, 90, ‘抠脚‘)}
# json.dumps 在内存中进行序列化
ret = json.dumps(dic, ensure_ascii=False)
# ensure_ascii码参数改为False是为了能正确显示中文
print(type(dic), dic)
print(type(ret), ret)

# json.loads 在内存中进行反序列化
res = json.loads(ret)
print(type(res), res)

# 输出如下:
# <class ‘dict‘> {‘大表哥‘: (190, 90, ‘抠脚‘)}
# 注:json中字符串的格式是一定要带双引号,不认单引号
# <class ‘str‘> {"大表哥": [190, 90, "抠脚"]}
# <class ‘dict‘> {‘大表哥‘: [190, 90, ‘抠脚‘]}
# 在json中没有元祖,另如果用土办法str转换的字符串,json也不认python中的单引号。

dump 和 load 是直接在内存中将对象序列化之后写入文件
依赖一个文件句柄

例:
import json
dic = {‘大表哥‘:(190, 90, ‘抠脚‘)}
f = open(‘json‘, ‘w‘, encoding=‘utf-8‘)
json.dump(dic, f, ensure_ascii=False)
# 先接收要序列化的对象,再接受文件句柄。
f.close()

f = open(‘json‘, ‘r‘, encoding=‘utf-8‘)
ret = json.load(f)
反序列化打开文件读取内容并转换回字典
print(type(ret), ret)
# <class ‘dict‘> {‘大表哥‘: [190, 90, ‘抠脚‘]}

dic1 = {‘铁乐猫‘:(168, 52, ‘下棋‘)}
f = open(‘json‘, ‘a‘, encoding=‘utf-8‘)
f.write(‘\n‘)
# 隔行写入
json.dump(dic1, f, ensure_ascii=False)
f.close()

json文件中此时存在有两行:
{"大表哥": [190, 90, "抠脚"]}
{"铁乐猫": [168, 52, "下棋"]}

json load的时候不认一行带多个字典的(会报错),所以要隔行写入字典。

小结:

dumps序列化,loads反序列化:只在内存中操作数据,

主要用于网络传输和多个数据与文件打交道。

dump序列化,load反序列化:主要用于一个数据直接存在文本里---直接和文件打交道。

json中不支持除了str数据类型之外的key,对元祖也不支持,会将元祖看成列表去操作。

小技巧:

如何让json显示得更好看(更像一个json)

例:
data = {‘username‘:[‘李雷‘, ‘韩梅梅‘], ‘idenu‘:‘student‘, ‘age‘:14}
json_dic2 = json.dumps(data, sort_keys=True, indent=4, separators=(‘,‘, ‘:‘), ensure_ascii=False)
print(json_dic2)

显示效果:
{
    "age":14,
    "idenu":"student",
    "username":[
        "李雷",
        "韩梅梅"
    ]
}

参数详解:
sort_keys=True 对(键)字母进行排序;
indent 行缩进几个字节;
separators=(‘,‘, ‘:‘) 对字典以指定的符号进行分隔;
ensure_ascii=False 显示中文。

Serialize obj to a JSON formatted str.(字符串表示的json对象) 

Skipkeys:默认值是False,
如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,
就会报TypeError的错误。此时设置成True,则会跳过这类key 。

ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,
只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。) 

indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,
否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json 

separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);
这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。 

sort_keys:将数据根据keys的值进行排序。 

pickle序列化模化

import pickle
# dumps 和 loads 在(网络传输)内存中互转
dic = {(185, 90, ‘抠脚‘):‘大表哥‘}
ret = pickle.dumps(dic)
# pickle序列化结果,不是一个可读的字符串,而是一个bytes类型
print(ret)
# 显示:b‘\x80\x03}q\x00K\xb9KZX\x06\x00\x00\x00\xe6\x8a\xa0\xe8\x84\x9aq\x01\x87q\x02X\t\x00\x00\x00\xe5\xa4\xa7\xe8\xa1\xa8\xe5\x93\xa5q\x03s.‘
print(pickle.loads(ret))
# 显示回{(185, 90, ‘抠脚‘): ‘大表哥‘}

# dump 和 load 在文件中写读操作
dic2 = {(168, 52, ‘下棋‘): ‘铁乐猫‘}
f = open(‘pickle‘, ‘wb‘)
# 使用pickle dump 必須以+b的形式打开文件
pickle.dump(dic2, f)
f.close()

f = open(‘pickle‘, ‘rb‘)
res = pickle.load(f)
# 使用load读取内容出来,且不需要再解码
print(res)
# 显示{(168, 52, ‘下棋‘): ‘铁乐猫‘}

关于序列化中多次读写的这种操作:

json在多次dump(不隔行写入多个元素)的时候,不能对应执行多次load来取出数据,而pickle可以。

json如果要写入多个元素,可以先将元素dumps序列化,f.write(序列化+‘\n‘)写入文件。

读出元素的时候,应该先按行读文件,在使用loads将读出来的字符串转换成对应的数据类型。

pickle强大之处还在于它支持python中的所有数据类型,比如一个对象:

一个对象序列化到文件中,把一个对象完完整整的放进到一个文件中。

(例如游戏里的存档和读档)

例:人狗大战模拟读存档人物属性状态

import pickle
class Animal:
    def __init__(self, name, hp, atk):
        self.name = name
        self.hp = hp
        self.atk = atk

class Person(Animal):
    def __init__(self, name, hp, atk, sex):
        super().__init__(name, hp, atk)
        self.sex = sex
    # 重写一下内置的双下str,让实例能被print打印出属性
    def __str__(self):
        return str(self.__dict__)

class Dog(Animal):
    def __init__(self, name, hp, atk, kind):
        super().__init__(name, hp, atk)
        self.kind = kind

hero = Person(‘铁乐‘, 100, 10, ‘男‘)
print(hero)
# 重写双下str后打印出{‘sex‘: ‘男‘, ‘name‘: ‘铁乐‘, ‘atk‘: 10, ‘hp‘: 100}

# 对象也可以被pickle序列化,这是在内存中操作的示例
ret = pickle.dumps(hero)
print(ret)
# 显示bytes类型的字节:
# b‘\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x03\x00\x00\x00atkq\x03K\nX\x04\x00\x00\x00nameq\x04X\x06\x00\x00\x00\xe9\x93\x81\xe4\xb9\x90q\x05X\x03\x00\x00\x00sexq\x06X\x03\x00\x00\x00\xe7\x94\xb7q\x07X\x02\x00\x00\x00hpq\x08Kdub.‘
res = pickle.loads(ret)
print(type(res), res)
# 重新输出成 <class ‘__main__.Person‘> {‘hp‘: 100, ‘atk‘: 10, ‘name‘: ‘铁乐‘, ‘sex‘: ‘男‘}

# 假如将对象保存进文件中操作会怎样?模拟游戏中对角色的状态存档操作:
f = open(‘save_hero‘, ‘wb‘)
# 实例在前,文件句柄在后
pickle.dump(hero, f)
# 对象以bytes类型存进了save_hero文件中
f.close()

# 模拟读档
f1 = open(‘save_hero‘, ‘rb‘)
# 读取save_hero,下面的obj就相当于是之前的hero实例,或也可直接命名回hero实例名
obj = pickle.load(f1)
print(type(obj), obj)
# <class ‘__main__.Person‘> {‘name‘: ‘铁乐‘, ‘hp‘: 100, ‘sex‘: ‘男‘, ‘atk‘: 10}

shelve模块

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

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

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

为什么用shelve?

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

使用json或者pickle持久化数据,能dump多次,但只能load一次,因为先前的数据已经被后面dump的数据覆盖掉了。

如果想要实现dump和load多次,就可以想到使用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(文件)。

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和load。

4、shelve访问己有key时,实际上取出的是数据源给出的一份拷贝,所以对于拷贝做出的增加和删除等操作都需要用writeback=True参数才能实现写入回源中进行修改。

5、shelve对于d[key] = data这种操作,视为存储数据,无则新增,有则覆盖,与访问key对当中的值(条目)进行修改默认不回写并不矛盾和冲突。

end

2018-4-21

原文地址:https://www.cnblogs.com/tielemao/p/8900548.html

时间: 2024-11-09 23:22:14

铁乐学python_day25_序列化模块的相关文章

铁乐学Python_Day34_Socket模块2和黏包现象

铁乐学Python_Day34_Socket模块2和黏包现象 套接字 套接字是计算机网络数据结构,它体现了C/S结构中"通信端点"的概念. 在任何类型的通信开始之前,网络应用程序必須创建套接字. 可以将它们比作成电话插孔,没有它将无法进行通信. 套接字最初是为同一主机上的应用程序所创建,使得主机上运行的一个程序(又名一个进程)与另一个运行的程序进行通信. 这就是所谓的进程间通信(Inter Process Communication, IPC). 有两种类型的套接字:基于文件的和面向网

python开发模块基础:序列化模块json,pickle,shelve

一,为什么要序列化 # 将原本的字典.列表等内容转换成一个字符串的过程就叫做序列化'''比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来.但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中.你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?没错序列化的过程就是从dic 变成str(dic)的

Python模块-logging、序列化模块、re模块

MarkdownPad Document logging模块 import logging   logging.debug('debug message')   logging.info('info message')   logging.warning('warning message')   logging.error('error message')   logging.critical('critical message') 运行结果: C:\Python36\python.exe C:

常用模块---sys&amp;logging&amp;序列化模块(json&amp;pickle)

sys 模块 sys.argv 命令行参数List,第一个元素是程序本身路径,通常用来避免io 阻塞 print('欢迎进入') info=sys.argv if info[index('-u')+1] == 'mona' and info[index('-p')+1] == '123': print('login successful') sys.exit(n) 退出程序,正常退出时exit(0) count=1 while count<10: if count == 8: sys.exit(

python模块之sys模块和序列化模块

sys模块 sys模块是与python解释器交互的一个接口 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys.exit(1) sys.version 获取Python解释程序的版本信息 sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 sys.platform 返回操作系统平台名称 序列化模块 序列化的目的: 以某种存储形式使自定义对象持久化 将对象从一个地方传递到另一个地

python__序列化模块

什么叫序列化--将原本的字典.列表等内容转换成一个字符串的过程就叫做序列化. 比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?现在我们能想到的方法就是存在文件里,然后另一个python程序再从文件里读出来.但是我们都知道,对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中.你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?没错序列化的过程就是从dic 变成str(dic)的过程.现在

sys模块 logging模块 序列化模块

一 :sys模块 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0) sys.version 获取Python解释程序的版本信息 sys.maxint 最大的Int值 sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 sys.platform 返回操作系统平台名称 二:logging模块 1 函数式简单配置 import logging logging.debug('debug messag

python常用模块之sys模块与序列化模块

一.sys模块 sys模块是一个与python解释器交互的模块,常用方法如下 sys.argv:用于程序运行时从程序外部接收参数,如果不传参数则显示的是程序的文件名 import sys name=sys.argv[1] password=sys.argv[2] if name=='jly' and password=='123': print('程序继续执行') else: sys.exit() -------------------------------------------------

Python之常用模块(re,时间,random,os,sys,序列化模块)(Day20)

一.时间模块 #常用方法 1.time.sleep(secs) (线程)推迟指定的时间运行.单位为秒. 2.time.time() 获取当前时间戳 在Python中表示时间的三种方式:时间戳,元组(struct_time), 格式化的时间字符串[时间戳为计算机能够识别的时间:时间字符串是人能够识别的时间:元组则是用来操作时间的] #导入时间模块 >>>import time #时间戳 >>>time.time() 1500875844.800804 #时间字符串 >