从Python的`dataclass`的`__repr__`中排除默认字段

7

概述

我有一个包含10个以上字段dataclass。使用print()命令会将有趣的内容深埋在默认值中 - 让我们通过不需要重复这些值来使它们更加友好。

Python中的Dataclasses

Python的@dataclasses.dataclass() (PEP 557)提供了自动可打印表示形式(__repr__())。

假设这个例子,基于python.org:

from dataclasses import dataclass


@dataclass
class InventoryItem:
    name: str
    unit_price: float = 1.00
    quantity_on_hand: int = 0

装饰器通过 @dataclass(repr=True)(默认)将会 print() 一个漂亮的输出:
InventoryItem(name='Apple', unit_price='1.00', quantity_on_hand=0)

我想要的是:跳过打印默认值。
`repr`会打印出所有字段,包括你不想显示的隐含默认值。
print(InventoryItem("Apple"))

# Outputs: InventoryItem(name='Apple', unit_price='1.00', quantity_on_hand=0)
# I want: InventoryItem(name='Apple')

print(InventoryItem("Apple", unit_price="1.05"))

# Outputs: InventoryItem(name='Apple', unit_price='1.05', quantity_on_hand=0)
# I want: InventoryItem(name='Apple', unit_price='1.05')

print(InventoryItem("Apple", quantity_on_hand=3))

# Outputs: InventoryItem(name='Apple', unit_price=1.00, quantity_on_hand=3)
# I want: InventoryItem(name='Apple', quantity_on_hand=3)

print(InventoryItem("Apple", unit_price='2.10', quantity_on_hand=3))

# Output is fine (everything's custom):
# InventoryItem(name='Apple', unit_price=2.10, quantity_on_hand=3)

讨论

在Python 3.10.4 中,dataclass repr 生成器的内部机制如下:cls.__repr__=_repr_fn(flds, globals)) -> _recursive_repr(fn)

如果需要关闭 @dataclass(repr=False) 并添加 def __repr__(self):,该怎么做呢?我们不想包含可选默认值。

背景

重申一下,在实践中,我的 dataclass 具有10多个字段

我正在通过运行代码和repl来print()实例,并在使用-vvv运行pytest时,使用@pytest.mark.parametrize

由于默认字段的存在,很难看到大型数据类的非默认值(有时是输入),更糟糕的是,每个非默认值都异常巨大且分散:遮盖了其他有价值的内容。

相关问题

截至今天,dataclass问题并不多(这可能会改变):

1个回答

6

你可以像这样做:

import dataclasses
from dataclasses import dataclass
from operator import attrgetter


@dataclass(repr=False)
class InventoryItem:
    name: str
    unit_price: float = 1.00
    quantity_on_hand: int = 0

    def __repr__(self):
        nodef_f_vals = (
            (f.name, attrgetter(f.name)(self))
            for f in dataclasses.fields(self)
            if attrgetter(f.name)(self) != f.default
        )

        nodef_f_repr = ", ".join(f"{name}={value}" for name, value in nodef_f_vals)
        return f"{self.__class__.__name__}({nodef_f_repr})"
        

# Prints: InventoryItem(name=Apple)
print(InventoryItem("Apple"))

# Prints: InventoryItem(name=Apple,unit_price=1.05)
print(InventoryItem("Apple", unit_price="1.05"))

# Prints: InventoryItem(name=Apple,unit_price=2.10,quantity_on_hand=3)
print(InventoryItem("Apple", unit_price='2.10', quantity_on_hand=3))

1
不错!我想知道 repr=False 是否真的必要(现在无法测试)- 定义 __repr__ 不会覆盖内置方法吗? - fsimonjetz
1
是的,就像你说的那样,这并不是必要的。然而,我将它留在那里作为一个“提醒”,我们实际上替换了 __repr__ - user2246849
@user2246849 这个可行!谢谢。您是否愿意在MIT或Python许可证下额外授权您答案中的代码?我想使用它(我仍然可以提供归属并链接到您的答案)。 - tony
1
@TonyN 当然可以,随意使用。我已经在我的个人资料中添加了许可证片段。 - user2246849
1
@user2246849 感谢您的回复和澄清!这是一个公正的观点,特别是如果 def __repr__ 藏在更复杂的类中的深处。 - fsimonjetz
1
以下是您的答案示例:定义libvcs.utils.dataclasses.SkipDefaultFieldsMixin,以及用法文档。我添加了doctests。当将其用作子类时,需要向@dataclasses.dataclass传递repr=False。直接使用__repr__时可以省略它。 - tony

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