簡體   English   中英

在ZOBD OOBTree中使用對象作為鍵的正確方法是什么?

[英]What is the correct way to use objects as keys in a ZOBD OOBTree?

在ZOBD中(在Python 3.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。

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()

在BTree或OOBTree中使用對象作為鍵的正確方法是什么? 我確實需要測試密鑰是否存在。

對於那些不知道的人來說,ZODB中的BTree非常類似於可擴展的Python字典(它們應該可以使用比常規Python字典更多的鍵值對),這些字典設計用於持久性。

我認為這個答案可以幫助您解決問題。

基本上,您必須在對象上重新實現三種方法:

  1. __eq__ (平等檢查)
  2. __ne__ (非平等檢查)
  3. __hash__使對象真正可序列化為字典鍵

雖然艾略特·貝里奧特的回答讓我得到了我需要的答案,但我想我會發布幫助我的完整答案,以便其他人不必花費額外的時間來解決問題。 (我將以第二人稱自言自語。)


首先(我沒有真正問過它,但這可能是你想要做的事情),不要繼承OOBTree或OOTreeSet(這會導致問題)。 創建自己繼承Persistent的類,並在其中放置OOBTree或OOTreeSet,如果你想要一個像繼承的OOBTree那樣的東西(同樣,如果你想要的話,定義使它看起來像字典或集合所需的方法)。

接下來,您需要創建一個Persistent ID系統(對於放在OOBTree或OOTreeSet中的每個對象,因為如果您沒有ZOBD可以跟蹤對象的唯一整數,對象會導致OOBTrees和OOTreeSets出現故障您需要定義Eliot提到的方法,以及其他類似的方法(這些方法需要比較整數ID - 而不是對象本身);即定義類的這些方法,這些方法生成的對象將是一個OOBTree或包含在OOTreeSet: __eq____ne____hash____lt____le____gt____ge__然而,為了有一個持續的ID,你將不得不作出一個ID計數器類或某事(因為它。由於某些奇怪的原因,我不會將普通整數保存為OOBTree中的值,除非我做錯了),並且該計數器類也必須有一個ID。

接下來,你需要確保如果你正在制作對象鍵,那么你最好不要在同一個OOBTree中使字符串成為鍵,否則你會遇到神秘的問題(由於字符串沒有與您的對象相同的ID系統)。 它會將字符串鍵與對象鍵進行比較,並導致錯誤,因為它們不是為了比較而設計的。

下面是一個Python 3.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()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM