2012-02-23 49 views
3

我想要做以下事情,定義兩個實例互相引用的類,如以下示例中的用戶和組。用戶可以屬於多個組,並且一個組可以包含多個用戶。實際的數據存儲在數據庫中,並且使用外鍵是一個簡單的多對多關係。沒有問題。使用zope.schema定義循環引用

之後,數據通過ORM加載並存儲在python對象的實例中。由於使用的ORM(SQLAlchemy)管理backrefs,所以仍然沒有問題。

現在我想檢查一下python對象是否符合使用zope.interface和zope.schema的某個接口。這是我陷入困境的地方。

import zope.schema as schema 
from zope.interface import Interface, implements 

class IGroup(Interface): 
    name = schema.TextLine(title=u"Group's name") 
# user_list = schema.List(title = u"List of Users in this group", value_type = sz.Object(IUser)) 

class IUser(Interface): 
    name = schema.TextLine(title=u"User's name") 
    group_list = schema.List(title = u"List of Groups containing that user", 
     value_type = schema.Object(IGroup)) 

IGroup._InterfaceClass__attrs['user_list'] = zs.List(title = u"List of Users in this group", required = False, value_type = zs.Object(IUser)) 

class Group(object): 
    implements(IGroup) 

    def __init__(self, name): 
     self.name = name 
     self.user_list = [] 

class User(object): 
    implements(IUser) 

    def __init__(self, name): 
     self.name = name 
     self.group_list = [] 

alice = User(u'Alice') 
bob = User(u'Bob') 
chuck = User(u'Chuck') 
group_users = Group(u"Users") 
group_auditors = Group(u"Auditors") 
group_administrators = Group(u"Administrators") 

def add_user_in_group(user, group): 
    user.group_list.append(group) 
    group.user_list.append(user) 

add_user_in_group(alice, group_users) 
add_user_in_group(bob, group_users) 
add_user_in_group(chuck, group_users) 
add_user_in_group(chuck, group_auditors) 
add_user_in_group(chuck, group_administrators) 

for x in [alice, bob, chuck]: 
    errors = schema.getValidationErrors(IUser, x) 
    if errors: print errors 
    print "User ", x.name, " is in groups ", [y.name for y in x.group_list] 

for x in [group_users, group_auditors, group_administrators]: 
    errors = schema.getValidationErrors(IGroup, x) 
    if errors: print errors 
    print "Group ", x.name, " contains users ", [y.name for y in x.user_list] 

我的問題是註釋行。我無法使用IUser定義IGroup,因爲那時IUser尚未定義。我已經找到了一個解決方法,在IUser的定義完成之後完成IGroup的定義,但根本不令人滿意,因爲IUser和IGroup是在不同的源文件中定義的,部分IGroup是在定義IUser的文件中定義的。

是否有任何正確的方法來做到這一點使用zope.schema?

回答

3

修改字段定義後:

#imports elided 

class IFoo(Interface): 
    bar = schema.Object(schema=Interface) 

class IBar(Interface): 
    foo = schema.Object(schema=IFoo) 

IFoo['bar'].schema = IBar 

的Martijn的回答似乎有點更加優美,自我記錄,但是這是一個比較簡潔的。兩者都不是完美的(相比之下,Django的解決方案是使用外鍵的字符串名稱) - 選擇你的毒藥。

恕我直言,這將是很好的指定一個接口,而不是一個標識符點名。如果您發現該方法有用,您可以非常輕鬆地爲此創建schema.Object的子類以供自己使用。

+1

正如我在評論中所說的,我不相信Martijn的答案真的可行(而我的解決方法肯定無效)。我不確定你的。理由:根據我的理解,IBar是在定義時使用IFoo字段的副本定義的,因此,在更改IFoo後,我不會更改嵌入的IFoo。然而,我並不確定... – kriss 2012-02-24 03:22:11

+1

是的,在Python中,原始類在這裏是猴子補丁,所以在導入接口模塊後,運行期間任何時候IFoo的所有使用者都將擁有一個具有正確綁定的對象字段用於驗證和內省的模式。這項技術已被用於多個生產項目並取得一致的成功。 – sdupton 2012-02-24 07:05:50

2

可以定義一個的基礎上,或抽象的,接口IUSER:

class IAbstractUser(Interface): 
    name = schema.TextLine(title=u"User's name") 

class IGroup(Interface): 
    name = schema.TextLine(title=u"Group's name") 
    user_list = schema.List(
     title=u"List of Users in this group", 
     value_type=schema.Object(IAbstractUser)) 

class IUser(IAbstractUser): 
    group_list = schema.List(
     title=u"List of Groups containing that user", 
     value_type=schema.Object(IGroup)) 

由於IUserIAbstractUser一個子類,實施前的對象也滿足後者接口。

編輯:你總是仍然可以申請sdupton的動態後 - 事實上改變了IGroup界面你定義IUSER後:

IGroup['user_list'].value_type.schema = IUser 

我仍然使用Abstract接口模式,以促進更好的代碼文檔。

+1

如果我理解正確的答案,user_list模式將不會檢查它的項目是IUser實現(即:與名稱和group_list),但只有他們是IAbstractUser(即:有一個名稱,但沒有檢查group_list)。我想這已經是我目前的解決方法,無論如何,你的建議更清潔。難道沒有辦法描述實際的循環引用而不輸入實際的循環嗎? – kriss 2012-02-23 21:59:34

+1

確實,user_list架構不會檢查IUser,只會檢查IAbstractUser,這是妥協。你可以把它和@sdupton的解決方案結合起來,並動態改變'user_list',以便在定義IUser之後只接受IUser,進一步收緊驗證器。這是Python中循環引用的本質,您需要在另一半之前的一半,然後將它們綁定在一起。 – 2012-02-24 07:54:42