Python循环导入再次出现(即这个设计有什么问题)

48 投票
5 回答
20640 浏览
提问于 2025-04-16 05:39

我们来看看Python(3.x)脚本的例子:

main.py:

from test.team import team
from test.user import user

if __name__ == '__main__':
    u = user()
    t = team()
    u.setTeam(t)
    t.setLeader(u)

test/user.py:

from test.team import team

class user:
    def setTeam(self, t):
        if issubclass(t, team.__class__):
            self.team = t

test/team.py:

from test.user import user

class team:
    def setLeader(self, u):
        if issubclass(u, user.__class__):
            self.leader = u

现在,当然,我遇到了循环导入的问题,导致了很糟糕的ImportError错误。

所以,作为一个对Python不太熟悉的人,我有三个问题。首先:

i. 我该怎么让这个东西正常工作?

而且,知道总会有人说“循环导入总是表明设计有问题”,所以第二个问题是:

ii. 这个设计为什么不好?

最后,第三个问题:

iii. 有什么更好的替代方案?

具体来说,上面的类型检查只是一个例子,还有一个基于类的索引层,这个索引可以用来找到所有属于某个团队的用户(用户类有很多子类,所以索引是双重的,既有针对所有用户的索引,也有针对每个特定子类的索引),或者找到所有包含特定用户的团队。

编辑:

我希望更详细的例子能澄清我想要实现的目标。为了便于阅读,省略了一些文件(但有一个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]

所以,除了这个不太好的循环导入问题之外,它运行得很好(实现细节省略了,但其实没什么复杂的)。

5 个回答

2

以下这些做法被认为是不好的习惯:

  • 可能不必要的类型检查(可以参考这里)。直接使用你得到的对象,就像它们是用户或团队一样,当出现问题时抛出异常(在大多数情况下,实际上会自动抛出异常,不需要额外的代码)。这样做可以避免循环导入的问题(至少目前是这样)。只要你得到的对象表现得像用户或团队,它们可以是任何东西。(鸭子类型
  • 类名用小写字母(这更多是个人喜好,但一般接受的标准(PEP 8)是不同的)。
  • 不必要的设置器:你可以直接说:my_team.leader=user_buser_b.team=my_team
  • 数据一致性的问题:如果出现 (my_team.leader.team!=my_team) 会怎样?
3

i. 要让它正常工作,你可以使用延迟导入。一个方法是保持user.py不变,把team.py改成:

class team:
    def setLeader(self, u):
        from test.user import user
        if issubclass(u, user.__class__):
            self.leader = u

iii. 另外一个办法是把team和user这两个类放在同一个文件里,这样也可以。

83

循环导入并不是一件坏事。代码中的 team 依赖于 user,而 user 又在处理 team 的事情,这种情况是很自然的。

这里更糟糕的做法是使用 from module import member。这意味着 team 模块在导入时想要获取 user 类,而 user 模块又想要获取 team 类。但问题是,当 user.py 被运行时,team 类还不存在,因为你还停留在 team.py 的第一行。

相反,应该只导入模块。这会让命名更清晰,也方便后续的修改,同时解决了导入的问题。因为你在导入时只导入了整个 模块,所以不需要担心里面的 还没有定义。等到你真正使用这个类的时候,它就会存在了。

例如,test/users.py:

import test.teams

class User:
    def setTeam(self, t):
        if isinstance(t, test.teams.Team):
            self.team = t

test/teams.py:

import test.users

class Team:
    def setLeader(self, u):
        if isinstance(u, test.users.User):
            self.leader = u

如果你想少写 test,那么 from test import teams 然后使用 teams.Team 也是可以的。这仍然是在导入一个模块,而不是模块中的某个成员。

另外,如果 TeamUser 相对简单,可以把它们放在同一个模块里。你不需要遵循 Java 中一个类对应一个文件的规则。使用 isinstance 测试和 set 方法让我觉得这有点像不太符合 Python 风格的 Java;根据你的需求,使用一个普通的、不进行类型检查的 @property 可能会更好。

撰写回答