使用自引用类型提示创建递归数据类

24

我想在Python中编写一个数据类定义,但无法在声明内部引用同一类。

主要想要实现的是嵌套结构的类型注释,如下所示:

 @dataclass
 class Category:
     title: str
     children: [Category] # I can't refer to a "Category"
  
 tree = Category(title='title 1', children=[
     Category('title 11', children=[]),
     Category('title 12', children=[])
 ])
1个回答

30

选项 #1

你可以将类名包装在字符串中,以便进行注释的 前向声明

from dataclasses import dataclass
from typing import List


@dataclass
class Category:
    title: str
    children: List['Category']

选项 #2

您可以包含一个__future__ 导入,这样所有注释默认都是像下面这样的前向声明。在这种情况下,您也可以消除typing导入,并在 Python 3.7 及以上版本中使用新式注释。

from __future__ import annotations

from dataclasses import dataclass


@dataclass
class Category:
    title: str
    children: list[Category]

4
关于选项#2的警告:PEP 563中的“新”类型提示风格被推迟成为Python 3.10的默认选项,因为另一个提案PEP 649指出了它存在一些缺点,并提供了一个稍微不同的解决方案。PEP 649似乎最终会被采纳,但目前尚不清楚过渡计划是什么。它应该为像这样的前向引用提供相同的好处,但可能需要不同的__future__导入才能启用。 - Blckknght
1
非常好的观点!我不知道 PEP 649,但这肯定是值得关注的提案。我特别支持后者的方法,由 PEP 563 引入,我认为这是解决递归注释问题的完美用例。 - rv.kvetch
annotations 的缺点是:它会使本来合法的代码因为 NameError: name 'Sub' is not defined 而失败:from __future__ import annotations # 如果注释掉这行就可以正常运行 import dataclasses from dataclasses import dataclass from dataclasses_serialization.json import JSONSerializer def test1(): # 如果将此函数移到模块级别,就可以正常运行 @dataclass class Sub: x: int @dataclass class Foo: s: Sub=None a=Foo(s=Sub(x=1)) j=JSONSerializer.serialize(a) a2=JSONSerializer.deserialize(Foo, j) test1() - timotheecour
@timotheecour 完全理解,我相信这可能是因为在函数内部,我认为类的全局范围仅限于函数可用的局部变量,而不是在模块级别。因此,我认为解决方法是使用函数本地值更新模块全局值。一个快速的修复和我经常在测试用例中使用的东西是 globals().update(locals()),它基本上只是更新了模块级别的全局值与函数(或测试用例)级别定义的任何类,直到那个点为止。 - rv.kvetch

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