Python高级技术之:`Python`的`shelve`模块:一个简单的键值对持久化方案。

各位观众老爷,大家好!今天咱们聊聊Python里一个挺好玩,也挺实用的小模块——shelve。 别看名字有点陌生,其实它干的活儿很简单,就是帮你把数据像书架一样,分门别类地保存起来,方便下次再用。

一、shelve是啥?能干啥?

简单来说,shelve模块提供了一种持久化存储方案,它可以让你像操作字典一样操作一个文件。这个文件可以存储Python的各种对象,比如列表、字典、甚至是你自定义的类的实例。 重要的是,它能把这些对象“腌制”好,保存到硬盘上,下次你想用的时候,再“解冻”出来,恢复原样。

你可以把shelve想象成一个简易的数据库,它不需要你安装什么复杂的数据库系统,也不需要你写SQL语句,只要用Python就能轻松搞定。

二、shelve的基本用法:像操作字典一样简单

  1. 打开(创建)一个shelve文件

    import shelve
    
    # 打开一个名为 'my_data' 的 shelve 文件。如果文件不存在,shelve 会自动创建它。
    # flag='c' 表示读写模式,如果文件不存在则创建;'r' 表示只读模式;'w' 表示只写模式(会清空原有内容);'n' 表示每次都创建一个新的空 shelve 文件
    with shelve.open('my_data', flag='c') as db:
        # db 现在就是一个类似字典的对象
        pass  # 这里先啥也不干,只是打开文件

    这段代码打开(或创建)了一个名为my_data的文件。注意,shelve.open()函数返回的对象,我们通常用with语句来管理,这样可以确保文件在使用完毕后自动关闭,避免数据丢失。

  2. shelve里存数据

    import shelve
    
    with shelve.open('my_data') as db:
        db['name'] = '张三'
        db['age'] = 30
        db['skills'] = ['Python', 'Java', 'C++']
        db['address'] = {'city': '北京', 'street': '长安街'}
    
        #  存入自定义类的实例
        class Person:
            def __init__(self, name, age):
                self.name = name
                self.age = age
    
        person = Person("李四", 25)
        db['person'] = person

    这段代码往my_data这个shelve文件里存了几个键值对。可以看到,键是字符串,值可以是各种Python对象。

  3. shelve里取数据

    import shelve
    
    with shelve.open('my_data') as db:
        name = db['name']
        age = db['age']
        skills = db['skills']
        address = db['address']
        person = db['person']
    
        print(f"姓名: {name}")
        print(f"年龄: {age}")
        print(f"技能: {skills}")
        print(f"地址: {address}")
        print(f"Person Name: {person.name}, Age: {person.age}") #访问自定义对象属性

    这段代码从my_data这个shelve文件里取出了之前存的数据,并打印出来。

  4. 删除shelve里的数据

    import shelve
    
    with shelve.open('my_data') as db:
        del db['age']  # 删除 'age' 对应的数据
    
        #判断键是否存在
        if 'age' in db:
            print("age 还在")
        else:
            print("age 没了")

    这段代码删除了my_data这个shelve文件里age对应的数据。

  5. 其他常用操作

    import shelve
    
    with shelve.open('my_data') as db:
        #  判断键是否存在
        if 'name' in db:
            print("name 存在")
    
        #  获取所有键
        keys = list(db.keys())
        print(f"所有键: {keys}")
    
        #  迭代 shelve 文件
        for key, value in db.items():
            print(f"键: {key}, 值: {value}")

三、shelve的进阶用法:玩转writebackprotocol

  1. writeback=True:懒人福音,但要注意性能

    默认情况下,shelve采用的是“懒写入”策略。也就是说,你修改了shelve里的某个对象,shelve并不会立即把修改后的对象写回硬盘。只有当你关闭shelve文件,或者显式地调用sync()方法时,shelve才会把修改后的对象写回硬盘。

    这样做的好处是提高了性能,减少了磁盘I/O。但坏处是,如果你在修改了对象之后,程序崩溃了,那么你的修改就丢失了。

    为了解决这个问题,shelve提供了一个writeback参数。如果你把writeback设置为True,那么shelve就会在你每次修改对象之后,立即把修改后的对象写回硬盘。

    import shelve
    
    with shelve.open('my_data', writeback=True) as db:
        db['my_list'] = [1, 2, 3]
        db['my_list'].append(4)  # 修改了列表
        # 由于 writeback=True,所以列表的修改会立即写回硬盘
        print(db['my_list']) # 输出 [1, 2, 3, 4]

    注意: writeback=True会显著降低性能,特别是当你频繁修改shelve里的对象时。所以,只有在你对数据安全要求很高,而且可以接受性能损失的情况下,才应该使用writeback=True

    如果你不使用writeback=True,但又想确保数据安全,可以手动调用sync()方法。

    import shelve
    
    with shelve.open('my_data') as db:
        db['my_list'] = [1, 2, 3]
        db['my_list'].append(4)  # 修改了列表
        db.sync()  # 手动把修改写回硬盘
        print(db['my_list']) # 输出 [1, 2, 3] , 未执行sync() 方法前
  2. protocol:选择合适的序列化协议

    shelve模块底层使用pickle模块来序列化和反序列化Python对象。pickle模块提供了几种不同的序列化协议,你可以通过protocol参数来选择使用哪种协议。

    不同的协议有不同的优缺点。一般来说,协议版本越高,序列化和反序列化的效率越高,但兼容性可能越差。

    import shelve
    import pickle
    
    # 使用 protocol=4,这是目前 pickle 的最高版本协议
    with shelve.open('my_data', protocol=pickle.HIGHEST_PROTOCOL) as db:
        db['my_data'] = {'a': 1, 'b': 2}

    一般来说,如果没有特殊需求,建议使用pickle.HIGHEST_PROTOCOL,它可以提供最佳的性能。

四、shelve的注意事项:坑还是要避开的

  1. 键必须是字符串

    shelve的键只能是字符串。如果你尝试使用其他类型的键,比如整数或元组,会引发TypeError

  2. 不要直接修改shelve里的对象

    这是一个非常重要的注意事项。由于shelve默认采用“懒写入”策略,所以你直接修改shelve里的对象,shelve并不会检测到你的修改,也不会把修改后的对象写回硬盘。

    import shelve
    
    with shelve.open('my_data') as db:
        db['my_list'] = [1, 2, 3]
        db['my_list'].append(4)  # 错误!这样做不会把修改写回硬盘
        print(db['my_list'])  # 输出 [1, 2, 3]

    正确的做法是,先把对象取出来,修改后再放回去。

    import shelve
    
    with shelve.open('my_data') as db:
        my_list = db['my_list']
        my_list.append(4)
        db['my_list'] = my_list  # 正确!先把对象取出来,修改后再放回去
        print(db['my_list']) # 输出 [1, 2, 3, 4]

    或者,使用writeback=True

  3. shelve不适合存储大量数据

    shelve底层使用dbm数据库来存储数据。dbm数据库是一种简单的键值对数据库,它不适合存储大量数据。如果你需要存储大量数据,建议使用专业的数据库,比如SQLite、MySQL或PostgreSQL。

  4. shelve不是线程安全的

    shelve不是线程安全的。如果你在多线程环境中使用shelve,可能会导致数据损坏。为了避免这种情况,你需要使用锁来保护shelve文件。

五、shelve的应用场景:小而美的解决方案

shelve模块虽然简单,但也有很多实用的应用场景。

  1. 缓存数据

    你可以使用shelve来缓存一些计算结果,避免重复计算。

    import shelve
    import time
    
    def expensive_function(x):
        """一个耗时的函数"""
        time.sleep(2)  # 模拟耗时操作
        return x * x
    
    def get_result(x):
        """从缓存中获取结果,如果缓存中没有,则计算并缓存"""
        with shelve.open('cache') as db:
            if str(x) in db:
                print("从缓存中获取")
                return db[str(x)]
            else:
                print("计算并缓存")
                result = expensive_function(x)
                db[str(x)] = result
                return result
    
    start_time = time.time()
    print(get_result(5))
    end_time = time.time()
    print(f"第一次调用耗时: {end_time - start_time:.2f}秒")
    
    start_time = time.time()
    print(get_result(5))
    end_time = time.time()
    print(f"第二次调用耗时: {end_time - start_time:.2f}秒")
  2. 存储用户配置

    你可以使用shelve来存储用户的配置信息。

    import shelve
    
    def save_config(username, config):
        """保存用户配置"""
        with shelve.open('user_config') as db:
            db[username] = config
    
    def load_config(username):
        """加载用户配置"""
        with shelve.open('user_config') as db:
            if username in db:
                return db[username]
            else:
                return None  # 如果用户没有配置,返回 None
    
    # 示例
    save_config('Alice', {'theme': 'dark', 'font_size': 12})
    config = load_config('Alice')
    print(config)
  3. 简单的会话管理

    在一些简单的Web应用中,你可以使用shelve来存储用户的会话数据。当然,对于复杂的Web应用,建议使用专业的会话管理方案。

六、shelve的替代方案:选择更合适的工具

shelve虽然方便,但也有一些局限性。在某些情况下,你可能需要选择其他的替代方案。

方案 优点 缺点 适用场景
pickle 简单易用,可以序列化任何Python对象 安全性问题,不适合存储来自不可信来源的数据 简单的数据持久化,不需要复杂的查询和事务
JSON 通用性好,易于与其他语言交互 只能序列化基本数据类型,不支持自定义对象 存储简单的配置信息,需要与其他语言交互
SQLite 轻量级数据库,支持SQL查询,事务 需要安装SQLite库,学习SQL语法 需要存储结构化数据,需要进行复杂的查询和事务
Redis 高性能的键值对数据库,支持多种数据结构 需要安装Redis服务器,学习Redis命令 需要高性能的缓存,需要支持多种数据结构
MongoDB 文档型数据库,支持复杂的查询,易于扩展 需要安装MongoDB服务器,学习MongoDB查询语法 需要存储半结构化数据,需要进行复杂的查询,需要易于扩展

七、总结:shelve,你的数据小助手

总而言之,shelve模块是一个简单易用的键值对持久化方案。它适用于存储少量数据,不需要复杂的查询和事务,以及对性能要求不高的场景。

希望今天的讲解能让你对shelve模块有更深入的了解。记住,选择合适的工具才能事半功倍!

下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注