Python shelve 忽略 writeback=True

1 投票
2 回答
1500 浏览
提问于 2025-04-18 06:30

我遇到了一个问题,就是在一个列表里的变化没有反映到关联的存储上(使用的是Python 2.6.9)。下面是一个简单的例子。

文件 Context.py:

import shelve

class Cont:
    shelf = shelve.open( STATUSFILE, protocol = 0, writeback = True )

文件 test.py:

from Context import Cont

class T:
    jobQueue = list()

    @classmethod
    def save(cls):
        cls.jobQueue = [1,2,3]
        Cont.shelf['queue'] = cls.jobQueue
        Cont.shelf.sync()
        print(Cont.shelf['queue'])
        cls.jobQueue.pop()
        print(Cont.shelf['queue'])
        print(id(cls.jobQueue))
        print(id(Cont.shelf['queue']))

输出结果:

[1,2,3]
[1,2,3]
7804904
7899472

首先,我原本以为第二个列表的输出是[1,2]。其次,为什么它们的ID不同呢?在给列表赋值的时候,应该只是复制一个引用,而ID应该保持不变。
奇怪的是,我在Python的命令行里无法重现这个问题。在那里,输出的ID是相同的,而且存储的列表也显示了jobQueue的变化。
看起来在执行我的程序时,writeback=True这个设置被忽略了。我非常感谢任何提示!

编辑:我在命令行里用这个简单的例子重现了这个问题。这可能和类的结构有关(我对面向对象的Python还很陌生)?我可以想象,类Cont并不知道类T,因此不能存储对T.jobQueue的引用,而是存储了列表的一个副本。

2 个回答

0

经过很多测试,我找到了问题所在:
这行代码 Cont.shelf.sync() 会改变存储在shelf里的数据的ID(和引用)。所以在调用 .sync() 之后,cls.jobQueue 的变化就不会被反映出来。
虽然shelve模块的文档并没有禁止我这样使用,但它似乎只有按照文档中的方式才能正常工作。这意味着所有的修改都应该这样进行:
Cont.shelf['queue'].pop()
或者在每次修改后,把 jobQueue 重新赋值给shelf。

4

如果不使用 shelf.sync():

def save(cls):
    Cont.shelf['queue'] = cls.jobQueue   #1
    print(Cont.shelf['queue'])           #2
    # [1, 2, 3]        
    cls.jobQueue.pop()                   #3
    print(Cont.shelf['queue'])           #4
    # [1, 2]
  1. 当设置为 writeback=True 时,赋值操作会把键值对同时存储在 shelf.cache 和 shelf.dict 中。
  2. 尝试从 shelf.cache 中获取键为 'queue' 的数据。
  3. 修改 cls.jobQueue,这个对象从缓存中获取的对象是同一个。
  4. 再次从 shelf.cache 中获取键为 'queue' 的数据。因为缓存中保存了对 cls.jobQueue 的引用,所以这次获取的还是同一个对象。

但是,如果你调用 shelf.sync():

def save(cls):
    Cont.shelf['queue'] = cls.jobQueue   
    Cont.shelf.sync()                    #1
    print(Cont.shelf['queue'])           #2
    # [1, 2, 3]        
    cls.jobQueue.pop()                   #3
    print(Cont.shelf['queue'])           #4
    # [1, 2, 3]
  1. 会更新 shelve 文件,并且缓存会被重置为空字典
  2. 尝试从 shelf.cache 中获取键为 'queue' 的数据。因为缓存是空的,所以会从 shelve 文件中获取数据的新副本
  3. 修改 cls.jobQueue,这个对象刚刚获取的副本不是同一个。
  4. 缓存仍然是空的,所以这次又会从未更新的 shelve 文件中获取一个新副本。

撰写回答