让我们考虑python (3.x)脚本:
main.py:
1 2 3 4 5 6 7 8 | from test.team import team from test.user import user if __name__ == '__main__': u = user() t = team() u.setTeam(t) t.setLeader(u) |
测试/ user.py:
1 2 3 4 5 6 | from test.team import team class user: def setTeam(self, t): if issubclass(t, team.__class__): self.team = t |
测试/ team.py:
1 2 3 4 5 6 | from test.user import user class team: def setLeader(self, u): if issubclass(u, user.__class__): self.leader = u |
现在,当然,我有循环导入和精彩的导入。
所以,不是毕达哥拉斯,我有三个问题。首先:
我怎么才能把这东西修好呢?
而且,知道有人会不可避免地说"循环导入总是表明设计问题",第二个问题就来了:
二世。为什么这个设计不好?
最后,第三个问题:
三世。还有更好的选择吗?
准确地说,上面的类型检查只是一个例子,还有一个基于类的索引层,它允许ie。查找所有属于一个团队的用户(user类有许多子类,因此索引加倍,对于一般用户和每个特定子类)或所有已将user作为成员的团队
编辑:
我希望更详细的例子将阐明我试图实现的目标。可达性省略了文件(但是有一个300kb的源文件让我有些害怕,所以请假设每个类都在不同的文件中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | # ENTITY class Entity: _id = None _defs = {} _data = None def __init__(self, **kwargs): self._id = uuid.uuid4() # for example. or randint(). or x+1. self._data = {}.update(kwargs) def __settattr__(self, name, value): if name in self._defs: if issubclass(value.__class__, self._defs[name]): self._data[name] = value # more stuff goes here, specially indexing dependencies, so we can # do Index(some_class, name_of_property, some.object) to find all # objects of some_class or its children where # given property == some.object else: raise Exception('Some misleading message') else: self.__dict__[name] = value def __gettattr__(self, name): return self._data[name] # USERS class User(Entity): _defs = {'team':Team} class DPLUser(User): _defs = {'team':DPLTeam} class PythonUser(DPLUser) pass class PerlUser(DPLUser) pass class FunctionalUser(User): _defs = {'team':FunctionalTeam} class HaskellUser(FunctionalUser) pass class ErlangUser(FunctionalUser) pass # TEAMS class Team(Entity): _defs = {'leader':User} class DPLTeam(Team): _defs = {'leader':DPLUser} class FunctionalTeam(Team): _defs = {'leader':FunctionalUser} |
现在来看一些用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | t1 = FunctionalTeam() t2 = DLPTeam() t3 = Team() u1 = HaskellUser() u2 = PythonUser() t1.leader = u1 # ok t2.leader = u2 # ok t1.leader = u2 # not ok, exception t3.leader = u2 # ok # now , index print(Index(FunctionalTeam, 'leader', u2)) # -> [t2] print(Index(Team, 'leader', u2)) # -> [t2,t3] |
因此,除了这个邪恶的循环导入之外,它工作得很好(省略了实现细节,但没有什么复杂的)。
循环进口本身并不是一件坏事。
这里最糟糕的做法是
相反,只导入模块。这将导致更清晰的命名空间,使以后的猴子补丁成为可能,并解决了导入问题。因为您只在导入模块时导入模块,所以您并不关心模块中还没有定义的类。等到你开始使用这门课的时候,你就会明白了。
因此,测试/ users.py:
1 2 3 4 5 6 | import test.teams class User: def setTeam(self, t): if isinstance(t, test.teams.Team): self.team = t |
测试/ teams.py:
1 2 3 4 5 6 | import test.users class Team: def setLeader(self, u): if isinstance(u, test.users.User): self.leader = u |
此外,如果
要使它工作,您可以使用一个延迟导入。一种方法是不使用user.py,将team.py更改为:
1 2 3 4 5 | class team: def setLeader(self, u): from test.user import user if issubclass(u, user.__class__): self.leader = u |
三世。另一种选择是,为什么不将团队和用户类放在同一个文件中呢?
不好的习惯/难闻的气味有以下几点:
可能不必要的类型检查(请参阅这里)。只要使用您作为用户/团队获得的对象,并在它中断时引发异常(或者在大多数情况下,在不需要额外代码的情况下引发异常)。如果不这样做,循环导入就会消失(至少现在是这样)。只要您获得的对象表现得像用户/团队,它们可以是任何对象。(Duck Typing)小写类(这或多或少取决于个人喜好,但是一般公认的标准(PEP 8)却有不同的做法在不需要的地方设置setter:您只需说:你可以修正依赖关系图;例如,用户可能不需要知道它是团队的一部分。大多数循环依赖关系都允许这样的重构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # team -> user instead of team <-> user class Team: def __init__(self): self.users = set() self.leader = None def add_user(self, user): self.users.add(user) def get_leader(self): return self.leader def set_leader(self, user): assert user in self.users, 'leaders must be on the team!' self.leader = user |
循环依赖显著地使重构复杂化,抑制代码重用,并减少测试中的隔离。
虽然在Python中可以通过在运行时导入、导入到模块级别或使用这里提到的其他技巧来绕过
这是我还没见过的东西。直接使用
下面是另一种解决方案:
1 2 3 4 5 6 7 8 9 | # main.py from test import team from test import user if __name__ == '__main__': u = user.User() t = team.Team() u.setTeam(t) t.setLeader(u) |
1 2 3 4 5 6 7 | # test/team.py from test import user class Team: def setLeader(self, u): if isinstance(u, user.User): self.leader = u |
1 2 3 4 5 6 7 8 | # test/user.py import sys team = sys.modules['test.team'] class User: def setTeam(self, t): if isinstance(t, team.Team): self.team = t |
并且文件
我开始喜欢这个想法,模块会变得很大,但是有一些函数和类是相互依赖的。假设有一个名为
好吧,在
1 2 3 4 | # mymodule/util.py from mymodule.private_util1 import Class1 from mymodule.private_util2 import Class2 from mymodule.private_util3 import Class3 |
然后在其他文件上:
1 2 3 4 5 | # mymodule/private_util1.py import sys util = sys.modules['mymodule.util'] class Class1(object): # code using other classes: util.Class2, util.Class3, etc |
1 2 3 4 5 | # mymodule/private_util2.py import sys util = sys.modules['mymodule.util'] class Class2(object): # code using other classes: util.Class1, util.Class3, etc |
只要先尝试导入
最后,我只指出这样做是为了帮助用户提高可读性(更短的文件),因此我不会说循环导入"天生"不好。所有的事情都可以在同一个文件中完成,但是我们使用它是为了分离代码,并且在滚动这个巨大的文件时不会混淆。