如何使用Python dataclasses为类记录构造函数?

65

我有一些现有的Python 3.6代码,想要迁移到Python 3.7的dataclasses。我有__init__方法并配有详细的文档说明,指定构造函数的属性和类型。

然而,如果我将这些类更改为使用Python 3.7中的新dataclasses,则构造函数是隐式的。在这种情况下,我该如何提供构造函数文档?虽然我很喜欢使用dataclasses,但是如果必须放弃清晰的文档才能使用它们,那就不太好了。

编辑以澄清我现在正在使用docstrings


看起来数据类会自动生成一个包含类定义中类型提示的类docstring,例如'C(name: str, number: int)',但是自动生成的__init__方法的docstring是None。因此,我想你可以在类定义之后手动分配__init__ docstring。虽然有点笨拙。 - snakecharmerb
如果类已经有文档字符串,那么这个自动生成的文档字符串就不会显示出来,这是可以接受的,因为人工提供的文档字符串通常比自动生成的更好。手动分配文档字符串肯定很笨拙,如果可能的话,我想避免这种情况,因此提出了这个问题。 - anahata
两点都是正确的。此外,手动分配的文档字符串对于运行时工具(如help)可以使用,但可能无法用于Sphinx等文档生成器。 - snakecharmerb
3个回答

57

拿破仑式文档字符串,正如 Sphinx文档所述(参见ExampleError类),明确涉及您的情况:

__init__方法可以在类级别的docstring中记录,也可以在__init__方法本身的docstring中记录。

如果不想要这种行为,则必须 明确地告诉Sphinx构造函数docstring和类docstring不是同一件事。

这意味着您可以将构造函数信息粘贴到类docstring的正文中。


如果您从docstrings构建文档,则可以实现以下粒度:

1)最低限度:

@dataclass
class TestClass:
    """This is a test class for dataclasses.

    This is the body of the docstring description.
    """
    var_int: int
    var_str: str

enter image description here

2) 构造函数的附加参数描述:

@dataclass
class TestClass:
    """This is a test class for dataclasses.

    This is the body of the docstring description.

    Args:
        var_int (int): An integer.
        var_str (str): A string.

    """
    var_int: int
    var_str: str

enter image description here

3) 附加属性描述:

@dataclass
class TestClass:
    """This is a test class for dataclasses.

    This is the body of the docstring description.

    Attributes:
        var_int (int): An integer.
        var_str (str): A string.

    """
    var_int: int
    var_str: str

enter image description here


当然,参数和属性描述也可以结合在一起,但由于数据类的属性应该是构造函数参数的直接映射,所以通常没有太多理由这样做。

在我看来,1)适用于小型或简单的数据类——它已经包括了构造函数签名及其相应类型,这对于数据类来说已经足够了。如果您想更多地说明每个属性,3)最好。


4
这非常深入,谢谢!我特别感谢提供了覆盖此情况的Sphinx文档的参考。 - anahata
@Arne谢谢!我启用了sphinx.ext.napoleon扩展,现在它可以正常工作了。 - woozly
更正 - 现在所有的类都会在Sphinx渲染的docstring中获取它们的classvars,而不仅仅是dataclasses。我将在这个新问题中跟进,并相应地更新这个答案。 - Arne
@bad_coder 谢谢,我刚看到。我需要更仔细地研究一下,但是由于我自动生成rst,所以我认为为每个dataclass添加异常不太实用。 - magomar
这可能是PyCharm的问题,但是这个解决方案会在IDE中导致“未解决的引用”错误,而@Edward的解决方案则不会。 - Dan Ciborowski - MSFT
显示剩余4条评论

8
我认为最简单的方法是:
@dataclass
class TestClass:
    """This is a test class for dataclasses.

    This is the body of the docstring description.

    """
    var_int: int  #: An integer.

    #: A string.
    #: (Able to have multiple lines.)
    var_str: str

    var_float: float
    """A float. (Able to have multiple lines.)"""

不确定 @Arne 的渲染结果为什么会是那样。在我的情况下,无论有没有文档字符串,数据类中的属性始终会显示。如下所示:

1) 最基本的:

2) 额外的构造函数参数描述:

3) 额外的属性描述:

可能是因为我在我的 conf.py(Sphinx v3.4.3,Python 3.7)中设置了一些错误:

extensions = [
    "sphinx.ext.napoleon",
    "sphinx.ext.autodoc",
    "sphinx_autodoc_typehints",
    "sphinx.ext.viewcode",
    "sphinx.ext.autosectionlabel",
]

# Napoleon settings
napoleon_google_docstring = True
napoleon_include_init_with_doc = True

1
不确定为什么@Arne渲染的结果看起来像那样。默认情况下,Sphinx如何呈现属性发生了变化:https://dev59.com/r1YItIcB2Jgan1znmdGd - Arne
这个解决方案适用于像PyCharm这样的IDE,而其他解决方案会引发未解决的引用错误。 - Dan Ciborowski - MSFT

7
数据类的一个主要优点是它们具有自我记录的功能。假设您代码的读者知道数据类的工作原理(并且您的属性已经适当命名),则类型注释的类属性应该成为构造函数的优秀文档。请参见官方数据类文档中的示例:
@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

如果您不希望代码读者了解数据类的工作原理,那么您可能需要重新考虑是否使用它们或在@ dataclass装饰器之后的内联注释中添加说明或链接到文档。如果您真的需要一个数据类的文档字符串,我建议将构造函数文档字符串放在类文档字符串中。对于上面的示例:

'''Class for keeping track of an item in inventory.

Constructor arguments:
:param name: name of the item
:param unit_price: price in USD per unit of the item
:param quantity_on_hand: number of units currently available
'''

嗯,这更加强调了如果属性真的要自我记录,命名它们的重要性。感谢您的建议! - anahata
@orn688 但是是否有可能完全从文档中删除 dataclass - Dmytro Chasovskyi
3
值得注意的是,help(InventoryItem)不会显示属性nameunit_pricequantity_on_hand及其文档字符串。 - Mike Holler

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