代码之家  ›  专栏  ›  技术社区  ›  ts.

python循环再次导入(也就是说,这个设计有什么问题)

  •  43
  • ts.  · 技术社区  · 14 年前

    让我们考虑一下python(3.x)脚本:

    MY.PY:

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

    测试/用户

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

    测试/团队

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

    当然,现在我有了循环导入和极好的导入错误。

    所以,我不是蟒蛇,我有三个问题。首先:

    我怎样才能使这东西工作?

    而且,知道有人不可避免地会说“循环导入总是表示设计问题”,第二个问题是:

    二。为什么这个设计不好?

    最后,第三个:

    什么是更好的选择?

    准确地说,如上所述的类型检查只是一个例子,还有一个基于类的索引层,它允许,即查找所有用户是一个团队的成员(用户类有许多子类,因此对于一般用户和每个特定子类,索引是加倍的),或者所有将用户作为成员给定的团队

    编辑:

    我希望更详细的例子能够阐明我试图实现的目标。为了可读性而省略的文件(但是拥有一个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 回复  |  直到 6 年前
        1
  •  79
  •   bobince    14 年前

    循环进口本身并不是一件坏事。这是自然的 team 依靠代码 user 同时 用户 做一些事情 团队 .

    更糟糕的做法是 from module import member . 这个 团队 模块正在尝试获取 用户 导入时的类,以及 用户 模块正在尝试获取 团队 班级。但是 团队 类还不存在,因为您仍然位于 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
    

    测试/团队

    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 方法还向我尖叫unPython Java疣;取决于你正在做什么,使用一个普通的、非类型的检查可能会更好。 @property .

        2
  •  3
  •   snapshoe    14 年前

    i.要使其生效,可以使用延迟导入。一种方法是让user.py单独存在,并将team.py更改为:

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

    对于另一种选择,为什么不把团队和用户类放在同一个文件中呢?

        3
  •  2
  •   Community CDub    8 年前

    坏习惯/臭味如下:

    • 可能不必要的类型检查( see also here )只需使用作为用户/团队获得的对象,并在异常中断时引发异常(或在大多数情况下,在不需要额外代码的情况下引发异常)。把这个放在一边,你就可以循环进口了(至少现在是这样)。只要你得到的东西 表现 就像用户/团队一样,它们可以是任何东西。( Duck Typing )
    • 低等级(这或多或少是一个品味问题,但一般公认的标准( PEP 8 )有什么不同吗
    • 不需要的地方设置:你可以说: my_team.leader=user_b user_b.team=my_team
    • 数据一致性问题:如果 (my_team.leader.team!=my_team) ?
        4
  •  0
  •   Community CDub    8 年前

    这是我还没见过的东西。这是一个坏主意/设计使用 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导入/读取一个附加模块的文件时 . 当我们进口时 test/user.py 模块 测试团队 将已定义,因为我们正在将其导入 main.py .

    我开始喜欢这个模块的概念,这些模块非常大,但是有一些函数和类相互依赖。假设有一个名为 util.py 这个文件包含许多相互依赖的类。也许我们可以在相互依赖的不同文件之间分割代码。我们如何绕过循环进口?

    嗯,在 U.T. 文件我们只是从其他“私有”文件导入所有对象,我说私有,因为这些文件不是直接访问的,而是通过原始文件访问的:

    # 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
    

    这个 系统模块 只要 mymodule.util 试图先导入。

    最后,我只会指出,这样做是为了帮助用户提高可读性(较短的文件),因此我不会说循环导入“本质上”是不好的。所有的事情都可以在同一个文件中完成,但是我们正在使用这个文件,这样我们就可以分离代码,并且在滚动浏览这个巨大的文件时不会弄混自己。

        5
  •  0
  •   chadlagore    6 年前

    您可以只修复依赖关系图;例如,用户可能不必知道它是团队的一部分。大多数循环依赖项都承认这种重构。

    # 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 通过在运行时导入、导入到模块级或使用这里提到的其他技巧,这些策略可以克服设计缺陷。尽可能避免循环进口是值得的。