让我们考虑python(3.x)脚本:
卖弄.朋友:
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:
from test.team import team
class user:
def setTeam(self, t):
if issubclass(t, team.__class__):
self.team = t
测试/ team.py:
from test.user import user
class team:
def setLeader(self, u):
if issubclass(u, user.__class__):
self.leader = u
现在,当然,我已经获得了循环导入和出色的ImportError。
所以,不是pythonista,我有三个问题。首先:
一世。我怎么能让这个东西工作?
并且,知道某人将不可避免地说“循环导入总是表明设计问题”,第二个问题来自:
II。为什么这个设计不好?
最后,第三个:
III。什么是更好的选择?
确切地说,上面的类型检查只是一个例子,还有一个基于类的索引层,它允许ie。发现所有用户都是一个团队的成员(用户类有许多子类,因此索引加倍,对于一般用户和每个特定子类)或所有团队都将用户作为成员
编辑:
我希望更详细的例子能够澄清我试图实现的目标。为了可读性省略了文件(但是有一个300kb的源文件让我感到害怕,所以请假设每个类都在不同的文件中)
# 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}
现在一些用法:
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]
所以,除了这个不合理的循环导入之外,它的工作原理很好(实现细节省略,但没有什么复杂的)。
循环进口本身并不是一件坏事。 team
代码依赖user
然而user
与team
做某事是很自然的。
更糟糕的做法是from module import member
。 team
模块试图在导入时获取user
类,而user
模块正试图获得team
类。但是team
类还不存在,因为当team.py
运行时你仍然在user.py
的第一行。
相反,只导入模块。这样可以使命名空间更清晰,以后可以进行猴子修补,并解决导入问题。因为您只是在导入时导入模块,所以您并不关心其中的类尚未定义。当你开始使用课程时,它将是。
所以,test / users.py:
import test.teams
class User:
def setTeam(self, t):
if isinstance(t, test.teams.Team):
self.team = t
测试/ teams.py:
import test.users
class Team:
def setLeader(self, u):
if isinstance(u, test.users.User):
self.leader = u
from test import teams
然后teams.Team
也行,如果你想写test
少。那仍然是导入模块,而不是模块成员。
另外,如果Team
和User
相对简单,请将它们放在同一个模块中。您不需要遵循Java每个文件的一个类别的习惯用法。 isinstance
测试和set
方法也尖叫unpythonic-Java-wart给我;取决于你正在做什么,你最好使用普通的,非类型检查的@property
。
一世。要使其工作,您可以使用延迟导入。一种方法是单独留下user.py并将team.py更改为:
class team:
def setLeader(self, u):
from test.user import user
if issubclass(u, user.__class__):
self.leader = u
III。或者,为什么不将团队和用户类放在同一个文件中呢?
不好的做法/臭有以下几点:
my_team.leader=user_b
和user_b.team=my_team
(my_team.leader.team!=my_team)
怎么办?这是我还没有看到的东西。使用sys.modules
直接是一个坏主意/设计吗?在阅读了@bobince解决方案后,我认为我已经理解了整个导入业务,但后来我遇到了一个与question类似的问题。
这是另一个解决方案:
# 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)
# test/team.py
from test import user
class Team:
def setLeader(self, u):
if isinstance(u, user.User):
self.leader = u
# test/user.py
import sys
team = sys.modules['test.team']
class User:
def setTeam(self, t):
if isinstance(t, team.Team):
self.team = t
并且文件test/__init__.py
文件为空。这有效的原因是因为test.team
首先被导入。 python正在导入/读取文件,它将模块附加到sys.modules
。当我们导入test/user.py
时,模块test.team
已经定义,因为我们在main.py
中导入它。
我开始喜欢这个想法,因为模块变得非常大,但是有相互依赖的函数和类。让我们假设有一个名为util.py
的文件,这个文件包含许多相互依赖的类。也许我们可以将代码分成彼此依赖的不同文件。我们如何绕过循环导入?
好吧,在util.py
文件中我们只是从其他“私有”文件导入所有对象,我说是私有的,因为这些文件不是要直接访问,而是我们通过原始文件访问它们:
# mymodule/util.py
from mymodule.private_util1 import Class1
from mymodule.private_util2 import Class2
from mymodule.private_util3 import Class3
然后在每个其他文件上:
# mymodule/private_util1.py
import sys
util = sys.modules['mymodule.util']
class Class1(object):
# code using other classes: util.Class2, util.Class3, etc
# mymodule/private_util2.py
import sys
util = sys.modules['mymodule.util']
class Class2(object):
# code using other classes: util.Class1, util.Class3, etc
只要首先尝试导入sys.modules
,mymodule.util
调用就会起作用。
最后,我只会指出这是为了帮助用户提高可读性(较短的文件),因此我不会说循环导入本质上是“坏”。一切都可以在同一个文件中完成,但我们正在使用它,以便我们可以分离代码,而不会在滚动浏览大文件时混淆自己。
你可以修复依赖图;例如,用户可能不必知道它是团队的一部分。大多数循环依赖都承认这种重构。
# 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中可以通过在运行时导入,导入到模块级别或使用此处提到的其他技巧来规避ImportError
,但这些策略可以解决设计缺陷。如果可能的话,值得避免循环进口。