循环依赖问题无法解决

92
以下代码会产生 NameError: name 'Client' is not defined。我该如何解决?
class Server:
    def register_client(self, client: Client)
        pass


class Client:
    def __init__(self, server: Server):
        server.register_client(self)

编写第三个类,仅实现服务器,再将其扩展为客户端。 - Norbert
2个回答

150
你可以通过使用一个尚未定义的Client类的字符串名称来使用前向引用
class Server:
    def register_client(self, client: 'Client')
        pass

截至Python 3.7版本, 您还可以通过在模块顶部添加以下__future__导入来推迟所有运行时注解的解析:

from __future__ import annotations

在此时,注释被存储为表达式的抽象语法树的字符串表示;您可以使用typing.get_type_hints()来解析这些注释(并解析上述使用的前向引用)。

有关详细信息,请参阅PEP 563 -- 注释的延迟评估


13
请注意,从Python 3.7开始,不再需要在此处使用字符串前向引用--取而代之的是,在代码中添加from __future__ import annotations导入语句以延迟注释的评估。这种行为显然将在 Python 4.0发布时默认启用。 - Michael0x2a
1
虽然我非常喜欢from __future__ import annotations的解决方案,但出于某种原因它对我不起作用:NameError: name 'TypeName' is not defined。Python版本是3.7。有任何想法为什么它可能不起作用吗? - Semisonic
1
为了阐述我的观点,如果您不在原地定义注释类型而是使用类型别名,则此解决方案将无法正常工作:MyType2 = typing.Mapping[str, MyType1]``` 在这种情况下,您仍然会收到“NameError”错误提示。 - Semisonic
当循环依赖在不同的文件中时,您该如何处理? - Gulzar
@Gulzar:你仍然可以使用整个路径的前向引用(“othermodule.OtherObject”),或者使用“if TYPE_CHECKING:”保护,只有在类型检查时才导入其他模块,如果由于类型提示之外的循环引用而导入其他模块是一个问题,则类型检查器会以不同的方式“import”。 - Martijn Pieters
显示剩余4条评论

6
如果您使用Python 3.7+,请使用from __future__ import annotations。 正如另一个答案中所提到的。然而,如果由于操作系统限制(例如Cygwin as of 2019-06-03)您还不能使用3.7,那么可以使用Forward References模块来解决这些前向/循环依赖项问题。
抱歉,这是一个编造的例子,但这应该说明了这种方法的实用性。
class Server():
    clients: list = None

    def __init__(self):
        self.clients=[]

    def register_client(self, client: 'Client') -> None:
        self.clients.append(client)
        print('Client `%s` registered with server' % client.name)

    def print_clients(self) -> None:
        for i, client in enumerate(self.clients):
            print('client %i: %s' % (i, client.name))

    @staticmethod
    def build_clone(server: 'Server') -> 'Server':
        svr_new: Server = Server()
        for client in server.clients:
            svr_new.register_client(client)
        return svr_new

class Client():
    name: str = None
    def __init__(self, name: str, server: 'Server'):
        self.name = name
        server.register_client(self)


svr = Server()
cli = Client('foo', svr)
svr.print_clients()

svr_clone = Server.build_clone(svr)
svr_clone.print_clients()

1
@urban - 我已经审查了pyre工具,并发现了一些与使用typing库相关的问题。看起来,pyre正在逐个案例处理问题。任何想编写简单测试用例并在pyre github页面上提出问题的人,都可能会得到解决。对我来说,我已经找到了一种方法来脱离Python 3.6,因此现在可以使用__future__中的注释库。祝你好运。 - Timothy C. Quinn
@TimothyC.Quinn:但即使在3.7之前,您也可以使用“'Server'”和“'Client'”。您不需要3.7才能正确注释它。 - user2357112
@Monica - 我进行了更新并测试了一下。这是你想要的吗? - Timothy C. Quinn
不,我的意思是字面上使用字符串 'Server''Client' 作为注释。例如:def register_client(self, client: 'Client') -> None: - user2357112
谢谢。我之前不知道PEP 484(https://www.python.org/dev/peps/pep-0484/#forward-references)。这是一个更好的解决方案!我会相应地进行更新。 - Timothy C. Quinn
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接