【问题标题】:How to iterate over attributes of dataclass in python?如何在python中迭代数据类的属性?
【发布时间】:2021-11-04 11:18:18
【问题描述】:

是否可以在 python 中迭代数据类实例的属性?比如我想在__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'

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

@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

【问题讨论】:

    标签: python class attributes iteration python-dataclasses


    【解决方案1】:

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

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

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

    【讨论】:

    • 直接使用__dataclass_fields__属性是不明智的,因为dataclasses的文档中没有提到该属性,这意味着dataclasses模块的作者认为该属性是一个实现细节他保留更改/删除的权利,恕不另行通知。最好使用 dataclasses.fields() 函数,它返回相同的东西,记录在案,并且是 OP 已经在做的事情。 docs.python.org/3/library/dataclasses.html
    【解决方案2】:

    您非常接近,但 dataclasses.fields 实际上返回了一个 Field 对象的元组。至少在我的情况下,返回类型似乎没有正确注释,但这很容易修复。

    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))
    

    【讨论】:

      【解决方案3】:

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

      >>> 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 在实例和类上的效果一样好。

      对于您的情况,您可以使用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))
      

      【讨论】:

        猜你喜欢
        • 2014-10-21
        • 1970-01-01
        • 2016-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多