让 Python 数据类可迭代?

sco*_*mcc 11 python python-dataclasses

我有一个数据类,我想在循环中迭代以吐出每个值。我可以__iter__()很容易地在其中写一个很短的内容,但这是我应该做的吗?我在文档中没有看到任何有关“可迭代”参数或任何内容的内容,但我只是觉得应该有......

这是我所拥有的,再次运行良好。

from dataclasses import dataclass

@dataclass
class MyDataClass:
    a: float
    b: float
    c: float

    def __iter__(self):
        for value in self.__dict__.values():
            yield value

thing = MyDataclass(1,2,3)
for i in thing:
    print(i)
# outputs 1,2,3 on separate lines, as expected
Run Code Online (Sandbox Code Playgroud)

这是最好/最直接的方法吗?

Sha*_*ger 15

最简单的方法可能是按照创建浅拷贝的函数dataclasses.astuple的指导迭代地提取字段,只需省略对 ( 的调用,将其保留为生成器表达式,这是返回tuple的合法迭代器:__iter__

def __iter__(self):
    return (getattr(self, field.name) for field in dataclasses.fields(self))

# Or writing it directly as a generator itself instead of returning a genexpr:
def __iter__(self):
    for field in dataclasses.fields(self):
        yield getattr(self, field.name)
Run Code Online (Sandbox Code Playgroud)

不幸的是,astuple它本身并不适合(因为它递归,解包嵌套的数据类和结构),而asdict(随后.values()对结果进行调用)虽然适合,但涉及急切地构造临时dict并递归复制内容,这是相对重量级的(内存-明智的和CPU方面的);最好避免不必要的O(n)急切工作。

asdict如果您想要/需要避免使用实时视图(如果实例的后续属性在迭代过程中被替换/修改,则asdict不会改变,因为它实际上保证它们是预先深度复制的,而genexpr将是合适的)当您达到新值时反映它们)。使用的实现asdict甚至更简单(如果更慢,由于急切的预深复制):

def __iter__(self):
    yield from dataclasses.asdict(self).values()

# or avoiding a generator function:
def __iter__(self):
    return iter(dataclasses.asdict(self).values())
Run Code Online (Sandbox Code Playgroud)

还有第三种选择,那就是dataclasses完全放弃。如果您同意让您的类表现得像一个不可变的序列,那么您可以通过将其设为 a typing.NamedTuple(或更旧的、不太灵活的collections.namedtuple)来免费获得可迭代性,例如:

from typing import NamedTuple

class MyNotADataClass(NamedTuple):
    a: float
    b: float
    c: float

thing = MyNotADataClass(1,2,3)
for i in thing:
    print(i)
# outputs 1,2,3 on separate lines, as expected
Run Code Online (Sandbox Code Playgroud)

并且它是自动迭代的(您也可以调用len它,索引它或切片它,因为它是tuple具有所有tuple行为的实际子类,它也通过命名属性公开其内容)。