如何正确使用对象作为ZOBD OOBTree中的键?

2024-06-16 10:04:37 发布

您现在位置:Python中文网/ 问答频道 /正文

在ZOBD(python3.x)中,我希望能够将对象作为键存储在BTrees.OOBTree.OOBTree()中。我尝试时遇到的错误示例(见注释):

from BTrees.OOBTree import OOBTree as Btree

class Test:
    pass

bt=Btree()
t=Test()
bt[t]=None #TypeError: Object has default comparison

因此,我在某个地方读到,可能需要定义__eq__来删除该错误,但是尽管这似乎修复了之前的问题,但它似乎导致了更多的问题。示例:

[编辑:应该注意的是,我在继承OOBTree(和TreeSet)时发现了一些问题。显然,它们没有正确保存;因此,这与继承Persistent不同,即使它们继承Persistent。]

^{pr2}$

在BTree或OOBTree中使用对象作为键的正确方法是什么?我确实需要测试一下钥匙是否也存在。在

对于那些不知道的人来说,ZODB中的btree非常类似于为持久性而设计的可伸缩Python字典(它们应该可以使用比普通Python字典更多的键值对)。在


Tags: 对象fromtestimport示例字典as错误
2条回答

虽然Eliot Berriot的回答让我找到了我需要的答案,但我想我会把帮助我的完整答案贴出来,这样其他人就不必花额外的时间来解决问题了。(我要用第二人称自言自语。)


首先,不要继承OOBTree或OOTreeSet(这会导致问题)。让自己的继承类持久化,如果你想要像继承的OOBTree一样的东西,在里面放一个OOBTree或OOTreeSet(如果你想要的话,也可以定义使它看起来像字典或集合所需的方法)。在

如果你不需要为每一个对象都创建一个完整的树,那么你就不需要为每一个对象创建一个完整的树。您需要定义Eliot提到的方法,以及其他一些类似的方法(这些方法需要比较整数ID而不是对象本身);也就是说,定义类的这些方法,这些方法将生成OOBTree的键或包含在OOTreeSet中的对象:__eq____ne____hash____lt____le____gt__,和__ge__。但是,除非你在类中有一个持久的ID,否则我就不能把它作为一个ID来保存,因为我没有在类中保存一个ID。在

其次,你需要确保如果你要把对象作为键,那么最好不要在同一个OOBTree中将字符串这样的东西也设为键,否则你会有神秘的问题(因为字符串与对象没有相同的ID系统)。它将比较字符串键和对象键,并导致错误,因为它们不是用来比较的。在

下面是python3.x代码的一个工作示例,它允许您将对象用作OOBTree中的键,并且允许您迭代OOBTree中的持久对象(并将它们用作键)。它还向您展示了如何保存和加载对象。在

抱歉,它有点长,但它应该能让你很好地了解它的工作原理:

import transaction, ZODB, ZODB.FileStorage
from persistent import Persistent
from BTrees.OOBTree import OOBTree as OOBTree
from BTrees.OOBTree import OOTreeSet as OOTreeSet

class Btree(Persistent):
    def __init__(self, ID=None, **attr):
        #I like to use entirely uppercase variables to represent ones you aren't supposed to access outside of the class (because it doesn't have the restrictions that adding _ and __ to the beginning do, and because you don't really need all caps for constants in Python)
        Persistent.__init__(self)
        self.DS=OOBTree() #DS stands for data structure
        self.DS.update(attr)
        if ID==None:
            self.ID=-1 #To give each object a unique id. The value, -1, is replaced.
            self.ID_SET=False
        else:
            self.ID=ID #You should remember what you’re putting here, and it should be negative.
            self.ID_SET=True
    def clear(self):
        self.DS.clear()
    def __delitem__(self, key):
        del self.DS[key]
    def __getitem__(self, key):
        return self.DS[key]
    def __len__(self):
        return len(self.DS)
    def __iadd__(self, other):
        self.DS.update(other)
    def __isub__(self, other):
        for x in other:
            try:
                del self.DS[x]
            except KeyError:
                pass
    def __contains__(self, key):
        return self.DS.has_key(key)
    def __setitem__(self, key, value):
        self.DS[key]=value
    def __iter__(self):
        return iter(self.DS)
    def __eq__(self, other):
        return self.id==other.id
    def __ne__(self, other):
        return self.id!=other.id
    def __hash__(self):
        return self.id
    def __lt__(self, other):
        return self.id<other.id
    def __le__(self, other):
        return self.id<=other.id
    def __gt__(self, other):
        return self.id>other.id
    def __ge__(self, other):
        return self.id>=other.id
    @property
    def id(self):
        if self.ID_SET==False:
            print("Warning. self.id_set is False. You are accessing an id that has not been set.")
        return self.ID
    @id.setter
    def id(self, num):
        if self.ID_SET==True:
            raise ValueError("Once set, the id value may not be changed.")
        else:
            self.ID=num
            self.ID_SET=True
    def save(self, manager, commit=True):
        if self.ID_SET==False:
            self.id=manager.inc()
        manager.root.other_set.add(self)
        if commit==True:
            transaction.commit()

class Set(Persistent):
    def __init__(self, ID=None, *items):
        Persistent.__init__(self)
        self.DS=OOTreeSet()
        if ID==None:
            self.ID=-1 #To give each object a unique id. The value, -1, is replaced automatically when saved by the project for the first time (which should be done right after the object is created).
            self.ID_SET=False
        else:
            if ID>=0:
                raise ValueError("Manual values should be negative.")
            self.ID=ID #You should remember what you’re putting here, and it should be negative.
            self.ID_SET=True
        self.update(items)
    def update(self, items):
        self.DS.update(items)
    def add(self, *items):
        self.DS.update(items)
    def remove(self, *items):
        for x in items:
            self.DS.remove(x)
    def has(self, *items):
        for x in items:
            if not self.DS.has_key(x):
                return False
        return True
    def __len__(self):
        return len(self.DS)
    def __iadd__(self, other):
        self.DS.update(other)
    def __isub__(self, other):
        self.remove(*other)
    def __contains__(self, other):
        return self.DS.has_key(other)
    def __iter__(self):
        return iter(self.DS)
    def __eq__(self, other):
        return self.id==other.id
    def __ne__(self, other):
        return self.id!=other.id
    def __hash__(self):
        return self.id
    def __lt__(self, other):
        return self.id<other.id
    def __le__(self, other):
        return self.id<=other.id
    def __gt__(self, other):
        return self.id>other.id
    def __ge__(self, other):
        return self.id>=other.id
    @property
    def id(self):
        if self.ID_SET==False:
            print("Warning. self.id_set is False. You are accessing an id that has not been set.")
        return self.ID
    @id.setter
    def id(self, num):
        if self.ID_SET==True:
            raise ValueError("Once set, the id value may not be changed.")
        else:
            self.ID=num
            self.ID_SET=True
    def save(self, manager, commit=True):
        if self.ID_SET==False:
            self.id=manager.inc()
        manager.root.other_set.add(self)
        if commit==True:
            transaction.commit()

class Counter(Persistent):
    #This is for creating a persistent id count object (using a plain integer outside of a class doesn't seem to work).
    def __init__(self, value=0):
        self.value=value
        self.ID_SET=False
        self.id=value
    #The following methods are so it will fit fine in a BTree (they don't have anything to do with self.value)
    def __eq__(self, other):
        return self.id==other.id
    def __ne__(self, other):
        return self.id!=other.id
    def __hash__(self):
        return self.id
    def __lt__(self, other):
        return self.id<other.id
    def __le__(self, other):
        return self.id<=other.id
    def __gt__(self, other):
        return self.id>other.id
    def __ge__(self, other):
        return self.id>=other.id
    @property
    def id(self):
        if self.ID_SET==False:
            print("Warning. self.id_set is False. You are accessing an id that has not been set.")
        return self.ID
    @id.setter
    def id(self, num):
        if self.ID_SET==True:
            raise ValueError("Once set, the id value may not be changed.")
        else:
            self.ID=num
            self.ID_SET=True

class Manager:
    def __init__(self, filepath):
        self.filepath=filepath
        self.storage = ZODB.FileStorage.FileStorage(filepath)
        self.db = ZODB.DB(self.storage)
        self.conn = self.db.open()
        self.root = self.conn.root
        print("Database opened.\n")
        try:
            self.root.other_dict #This holds arbitrary stuff, like the Counter. String keys.
        except AttributeError:
            self.root.other_dict=OOBTree()
            self.root.other_dict["id_count"]=Counter()
        try:
            self.root.other_set #set other
        except AttributeError:
            self.root.other_set=OOTreeSet() #This holds all our Btree and Set objects (they are put here when saved to help them be persistent).
    def inc(self): #This increments our Counter and returns the new value to become the integer id of a new object.
        self.root.other_dict["id_count"].value+=1
        return self.root.other_dict["id_count"].value
    def close(self):
        self.db.pack()
        self.db.close()
        print("\nDatabase closed.")

class Btree2(Btree):
    #To prove that we can inherit our own classes we created that inherit Persistent (but inheriting OOBTree or OOTreeSet causes issues)
    def __init__(self, ID=None, **attr):
        Btree.__init__(self, ID, **attr)




m=Manager("/path/to/database/test.fs")

try:
    m.root.tree #Causes an AttributeError if this is the first time you ran the program, because it doesn't exist.
    print("OOBTree loaded.")
except AttributeError:
    print("Creating OOBTree.")
    m.root.tree=OOBTree()
    for i in range(5):
        key=Btree2()
        key.save(m, commit=False) #Saving without committing adds it to the manager's OOBTree and gives it an integer ID. This needs to be done right after creating an object (whether or not you commit).
        value=Btree2()
        value.save(m, commit=False)
        m.root.tree[key]=value #Assigning key and value (which are both objects) to the OOBTree
    transaction.commit() #Commit the transactions

try:
    m.root.set
    print("OOTreeSet loaded.")
except AttributeError:
    print("Creating OOTreeSet")
    m.root.set=OOTreeSet()
    for i in range(5):
        item=Set()
        item.save(m, commit=False)
        m.root.set.add(item)
    transaction.commit()

#Doing the same with an OOTreeSet (since objects in them suffered from the same problem as objects as keys in an OOBTree)
for x in m.root.tree:
    print("Key: "+str(x.id))
    print("Value: "+str(m.root.tree[x].id))
    if x in m.root.tree:
        print("Comparison works for "+str(x.id))

print("\nOn to OOTreeSet.\n")

for x in m.root.set:
    if x in m.root.set:
        print("Comparison works for "+str(x.id))

m.close()

我想this answer可以帮你解决问题。在

基本上,您必须在对象上重新实现三个方法:

  1. __eq__(相等性检查)
  2. __ne__(不相等检查)
  3. __hash__使对象真正可序列化为字典键

相关问题 更多 >