python 的 NamedTuple return 结构是少数几个应该使用可变默认值的地方之一吗?

Are python's NamedTuple return structures one of the few places where mutable defaults should be used?

从 python 函数到 return 结构的方法已在各种帖子中进行了详细讨论。两个不错的 here and here.

但是,除非我错过了它,none 提议的解决方案在设置其成员的同一位置定义结构,而是在分配时重复成员列表(不是 DRY)或依赖位置(容易出错)。

我正在寻找一种 DRY 方法来执行此操作,既可以提高写入速度,又可以避免重复自己时常见的参数未对齐错误。

下面的代码片段显示了执行此操作的三种尝试。为了简洁起见,示例的结构只包含一个元素,但意图显然是结构包含多个元素。

这三个方法是 DRY,将结构定义嵌入到 returned 实例的初始化中。

方法 1 强调了对更好方法的需求,但说明了 DRY 寻求的语法,其中结构及其填充方式(在 运行 时间决定)位于同一位置,即 dict()呼唤。

方法 2 使用 typing.NamedTuple 并且似乎有效。但是它使用可变默认值来做到这一点

方法 3 遵循方法 2 的方法,使用 dataclasses.dataclass 而不是 typing.NamedTuple。它失败了,因为前者明确禁止可变默认值,引发 ValueError: mutable default is not allowed

from collections import namedtuple
from dataclasses import dataclass
from typing import NamedTuple, List, Tuple

# Method 1
def ret_dict(foo_: float, bar_: float) -> Tuple:
    return_ = dict(foo_bar=[foo_, bar_])
    _ = namedtuple('_', return_.keys())
    return _(*return_.values())


# Method 2
def ret_nt(foo_: float, bar_: float) -> 'ReturnType':
    class ReturnType(NamedTuple):
        foo_bar: List[float] = [foo_, bar_]     # Mutable default value allowed
    return ReturnType()


# Method 3
def ret_dc(foo_: float, bar_: float) -> 'ReturnType':
    @dataclass
    class ReturnType:
        foo_bar: List[float] = [foo_, bar_]   # raises ValueError: mutable default is not allowed
    return ReturnType()


def main():
    rt1 = ret_dict(1, 0)
    rt1.foo_bar.append(3)
    rt2 = ret_dict(2, 0)
    print(rt1)
    print(rt2)

    rt1 = ret_nt(1, 0)
    rt1.foo_bar.append(3)   # amending the mutable default does not affect subsequent calls
    rt2 = ret_nt(2, 0)
    print(rt1)
    print(rt2)

    rt1 = ret_dc(1, 0)
    rt1.foo_bar.append(3)  # amending the default does not affect subsequent calls
    rt2 = ret_dc(2, 0)
    print(rt1)
    print(rt2)


if __name__ == "__main__":
    main()

出现以下问题:

方法 2 是明智的 pythonic 方法吗?

一个问题是可变默认值有点禁忌,尤其是对于函数参数。然而,我想知道它们在这里的使用是否合适,因为附加的代码表明这些 NamedTuple 默认值(可能还有整个 ReturnType 定义)在每次函数调用时都会被评估,这与它的函数参数默认值相反在我看来只评估一次并永远存在(因此出现问题)。

另一个问题是 dataclasses 模块似乎已经特意明确禁止这种用法。在这种情况下,这个决定是否过于教条?或者是否有理由反对方法 2?

这样效率低吗?

如果方法 2 的语法意味着:

,我会很高兴

1 - 仅在第一遍定义 ReturnType 一次

2 - 在每次传递时使用给定的(动态设置的)初始化调用 __init__()

不过,恐怕它的意思可能是:

1 - 定义 ReturnType 及其每次传递的默认值

2 - 在每次传递时使用给定的(动态设置的)初始化调用 __init__()

当调用处于“紧密”循环中时,是否应该担心在每次传递时重新定义矮胖的 ReturnTypes 的效率低下?每当在函数内部定义 class 时,是否会出现这种低效率?应该在函数内部定义 classes 吗?

是否有(希望是好的)方法来使用新的 dataclasses 模块 (python 3.7) 实现 DRY 定义实例化?

最后,有没有更好的DRY定义-实例化语法?

However, I am afraid that it may instead mean the following:

1 - Define ReturnType and its defaults on every pass

2 - call __init__() with the given (dynamically set) initialization on every pass

就是这个意思,又要花很多时间和space。此外,它会使您的注释无效 - -> 'ReturnType' 需要模块级别的 ReturnType 定义。它还会破坏酸洗。

坚持使用模块级别 ReturnType,不要使用可变默认值。或者,如果您想要的只是通过点符号访问成员,而您并不真正关心制作有意义的类型,只需使用 types.SimpleNamespace:

return types.SimpleNamespace(thing=whatever, other_thing=stuff)