Python 在ZOBD OOBTree中使用对象作为键的正确方法是什么?

Python 在ZOBD OOBTree中使用对象作为键的正确方法是什么?,python,python-3.x,zodb,Python,Python 3.x,Zodb,在ZOBD(Python3.x)中,我希望能够将对象作为键存储在BTrees.OOBTree.OOBTree()中。我尝试时遇到的错误示例(请参见注释): 因此,我在某个地方读到,可能需要定义\uuuuu eq\uuuu来删除该错误,但尽管这似乎解决了前面的问题,但似乎会导致更多问题。例如: [编辑:应该注意的是,我在这里发现了继承OOBTree(和TreeSet)的一些问题。显然,它们没有正确保存;因此,这与继承Persistent不同,即使它们继承Persistent。] from BTre

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

因此,我在某个地方读到,可能需要定义
\uuuuu eq\uuuu
来删除该错误,但尽管这似乎解决了前面的问题,但似乎会导致更多问题。例如:

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

from BTrees.OOBTree import OOBTree as Btree

class Test:
    def __eq__(self, other): #Maybe this isn't the way to define the method
        return self==other

bt=Btree()
t=Test()
bt[t]=None

t in bt #TypeError: unorderable types: Test() < Test()
从BTrees.OOBTree导入OOBTree作为Btree
课堂测试:
定义(自我,其他):#也许这不是定义方法的方式
返回self==其他
bt=Btree()
t=测试()
bt[t]=无
bt中的t#TypeError:无序类型:Test()
在BTree或OOBTree中将对象用作键的正确方法是什么?我也需要测试密钥是否存在

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

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

  • \uuuuu eq\uuuu
    (相等性检查)
  • \uuuu ne\uuuuu
    (非相等检查)
  • \uuuu散列\uuuu
    使对象真正可序列化为字典键

  • 虽然艾略特·贝里奥特的回答让我找到了我需要的答案,但我想我会发布完整的答案,这样其他人就不必花额外的时间去弄清楚。(我要用第二人称对自己说。)


    首先,不要继承OOBTree或OOTreeSet(这会导致问题)。如果您想要类似继承的OOBTree的东西,请创建您自己的继承持久的类,并在其中放置OOBTree或OOTreeSet(如果您想要,还可以定义使其看起来像字典或集合的方法)

    接下来,您需要为放入OOBTree或OOTreeSet的每个对象创建一个持久的ID系统(因为如果您没有一个唯一的整数ZOBD可以跟踪您的对象,那么对象会导致OOBTree和OOTreeSet发生故障)。您需要定义Eliot提到的方法,以及其他一些类似的方法(这些需要比较整数ID,而不是对象本身)例如,定义类的这些方法,这些方法生成对象,这些对象将作为OOBTree的键或包含在ootreeseet中:
    \uuuuueq\uuu
    \uune\uuuuuu
    \uuuu散列\uuuuuuu
    \uuuuule\uuucode>、
    \uugt\ucode>、以及
    \ucode>比如一个ID计数器类之类的(因为出于某种奇怪的原因,它不会将普通整数保存为OOBTree中的值,除非我做错了),并且该计数器类也必须有一个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()
    
    导入事务,ZODB,ZODB.FileStorage
    从持久导入持久
    从BTrees.OOBTree导入OOBTree作为OOBTree
    从BTrees.OOBTree导入OOTreeSet作为OOTreeSet
    B类树(持久):
    定义初始化(self,ID=None,**attr):
    #我喜欢使用完全大写的变量来表示您不应该在类之外访问的变量(因为它没有在开头添加u和u_u的限制,而且在Python中不需要所有常量的大写)
    持久性。初始化(自)
    self.DS=OOBTree()#DS代表数据结构
    self.DS.update(attr)
    如果ID==无:
    self.ID=-1#给每个对象一个唯一的ID。值-1被替换。
    self.ID\u SET=False
    其他:
    self.ID=ID#你应该记住你在这里放的东西,应该是负数。
    self.ID\u SET=True
    def清除(自):
    self.DS.clear()
    def uu delitem uu(self,key):
    del self.DS[键]
    def _u获取项目(自身,密钥):
    返回self.DS[键]
    定义(自我):
    返回len(self.DS)
    定义(自我、其他):
    self.DS.update(其他)
    定义(自我、其他):
    对于其他中的x:
    尝试:
    del self.DS[x]
    除KeyError外:
    通过
    def___;包含_______;(自身,密钥):
    返回self.DS.has_键(key)
    定义设置项(自身、键、值):
    self.DS[key]=值
    定义(自我):
    返回iter(self.DS)
    定义(自身、其他):
    返回self.id==other.id
    定义(自身、其他):
    返回self.id!=其他.id
    定义散列(自我):
    返回self.id
    定义(自身、其他):
    返回self.id=other.id
    @财产
    def id(自身):
    如果self.ID_SET==False:
    打印(“Warning.self.id\u set为False。您正在访问尚未设置的id。”)
    返回self.ID
    @id设置器
    def id(self,num):
    如果self.ID_SET==True:
    raise VALUERROR(“一旦设置,id值可能不会更改。”)
    
    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()