如何迭代 python 中数据类的属性?

How to iterate over attributes of dataclass in python?

是否可以在 python 中迭代数据 class 实例的属性?例如,我想在 __post_init__ 中加倍整数属性:

from dataclasses import dataclass, fields
@dataclass
class Foo:
    a: int
    b: int

    def __post_init__(self):
        self.double_attributes()

    def double_attributes(self):
        for field in fields(Foo):
            field = field*2

x = {
    'a': 1,
    'b': 2
}
y = Foo(**x)

>>> TypeError: unsupported operand type(s) for *: 'Field' and 'int'

如何访问 class 实例的值并将其设置为类似于下面但在循环中的其他值?

@dataclass
class Foo:
    a: int
    b: int

    def __post_init__(self):
        self.double_a()
        self.double_b()

    def double_a(self):
        self.a = self.a*2
    def double_b(self):
        self.b = self.b*2

是的,这是可能的。你可以这样做

def double_attributes(self):
  for field in self.__dataclass_fields__:
    value = getattr(self, field)
    setattr(self, field, value * 2)

__dataclass_fields__ returns 包含对象所有字段的字典。然后,您可以使用 getattr 来检索每个字段的值,并使用 setattr 来按名称更改每个字段的值。

你非常接近,但 dataclasses.fields 实际上 return 是一个 Field 对象的元组。至少在我的例子中,return 类型似乎没有正确注释,但这很容易修复。

from dataclasses import dataclass, fields, Field
from typing import Tuple


@dataclass
class Foo:
    a: int
    b: int

    def __post_init__(self):
        self.double_attributes()

    def double_attributes(self):

        # Note the annotation added here (tuple of one or more
        # `dataclasses.Field`s)
        cls_fields: Tuple[Field, ...] = fields(self.__class__)
        
        for field in cls_fields:

            # This check is to avoid fields annotated with other types
            # such as `str`
            if issubclass(field.type, int):
                new_val = getattr(self, field.name) * 2
                setattr(self, field.name, new_val)

但是如果您多次 运行(例如创建许多 Foo 对象),那么缓存整数字段列表可能会稍微有效一些。例如,以下是我可能建议的伪代码:

integer_fields: ClassVar[Frozenset[Field]] = frozenset(f for f in fields(cls) if issubclass(f.type, int))

我认为最简单的方法是使用 typing.get_type_hints 检索 实例的注释 ,而不是 class 的字段get_type_hints returns 字典映射 class 属性到它们被注释的类型。例如:

>>> from typing import get_type_hints
>>> 
>>> class Bar:
...     x: int
...     y: str
... 
>>> get_type_hints(Bar)
{'x': <class 'int'>, 'y': <class 'str'>}
>>> b = Bar()
>>> get_type_hints(b)
{'x': <class 'int'>, 'y': <class 'str'>}

如您所见,get_type_hints 在实例上的效果与在 classes 上一样好。

对于您的情况,您可以使用 get_type_hints 来解决您的情况,如下所示:

from dataclasses import dataclass
from typing import get_type_hints

@dataclass
class Foo:
    a: int
    b: int

    def __post_init__(self):
        self.double_attributes()

    def double_attributes(self):
        for field_name, field_type in get_type_hints(self).items():
            if issubclass(field_type, int):
                current_val = getattr(self, field_name)
                setattr(self, field_name, (current_val * 2))