你可以控制在Python中将dataclasses转换为字典时属性的顺序吗?

huangapple go评论68阅读模式
英文:

How can I control the order of attributes when converting dataclasses to a dict in Python?

问题

我想在我的项目中使用命名数据,但最终我需要将数据转换为CSV文件。

我的方法是在代码中使用数据类的数据容器列表。最后,数据类的列表转换为字典列表,然后转换为CSV文件。

我希望能控制最终数据对象中属性出现的顺序。这是我的代码:

from dataclasses import dataclass

@dataclass
class MyClass:
    c: str
    b: str
    a: str = "Default"

objdata = [
    MyClass(c="Foo", b="Bar"),
    MyClass(c="Test", b="Test"),
    MyClass(c="asdf", b="yxcv")
]

dictdata = []
attributes = [a for a in dir(objdata[0]) if not a.startswith('__')]
for entry in objdata:
    csv_entry = {}
    for attribute in attributes:
        csv_entry[attribute] = getattr(entry, attribute)
    dictdata.append(csv_entry)

我有几个具有不同属性的数据类。我想重用将数据类转换为字典的代码,因此我循环遍历类中的属性。问题是dir按字母顺序返回属性(所以是a、b、c),而不是我在类中声明它们的顺序(它们是c、b、a)。有办法控制这个顺序吗?

英文:

i want to used named data in my project but in the end i have to convert the data to a CSV file.

My approach is to use list of dataclasses in the code as data containers. In the end the list of dataclasses is converted to a list of dicts and from that to a CSV file.

I want to control the order in which the attributes appear in the final data object. Here is my code:

from dataclasses import dataclass

@dataclass
class MyClass:
    c: str
    b: str
    a: str = "Default"

objdata = [
    MyClass(c="Foo", b="Bar"),
    MyClass(c="Test", b="Test"),
    MyClass(c="asdf", b="yxcv")
    ]

dictdata = []
attributes = [a for a in dir(objdata[0]) if not a.startswith('__')]
for entry in objdata:
    csv_entry = {}
    for attribute in attributes:
        csv_entry[attribute] = getattr(entry, attribute)
    dictdata.append(csv_entry)

I have several dataclasses with different attributes. I want to reuse the code converting the dataclass to a dict, so i loop over the attributes in the class. The problem is dir returns the attributes in alphabetical order (so a, b c) and not in the order i declared them in the class (which is c, b, a). Is there a way to control this?

答案1

得分: 1

在Python 3.11.3中,属性可以按照声明的顺序获取,如下所示:

from dataclasses import dataclass

@dataclass
class A:
    c: str
    b: str
    a: str

a = A(a='A', c='C', b='B')

for key in a.__dict__:
    print(key)

输出:

c
b
a
英文:

In Python 3.11.3 the attributes can be acquired in order of declaration thus:

from dataclasses import dataclass

@dataclass
class A:
    c: str
    b: str
    a: str

a = A(a='A', c='C', b='B')

for key in a.__dict__:
    print(key)

Output:

c
b
a

答案2

得分: 1

所以,你只需要使用dataclasses.asdict,它为任何由使用dataclasses.dataclass代码生成器装饰的类创建的对象实例实现了这种行为。

import dataclasses

@dataclasses.dataclass
class MyClass:
    c: str
    b: str
    a: str = "Default"

objdata = [
    MyClass(c="Foo", b="Bar"),
    MyClass(c="Test", b="Test"),
    MyClass(c="asdf", b="yxcv")
]

for obj in objdata:
    print(dataclasses.asdict(obj))

注意,这是通过使用dataclasses.fields(obj)来实现的,它最终只是访问了__dataclass_fields__类变量。详情可以在源代码中找到。

如果你只想将你知道是某个特定dataclass类型的每个对象写入CSV文件,那么无需创建中间字典。你可以这样做:

import operator
import csv

fields = [f.name for f in dataclasses.fields(MyClass)]
getter_func = operator.attrgetter(*fields)

with open("data.csv", "w") as f:
    writer = csv.writer(f)
    writer.writerow(fields)  # 如果你需要标题行
    writer.writerows(map(getter_func, objdata))
英文:

So, you should just use dataclasses.asdict, which implements this behavior for any object that is an instance of a class created by a class that was decorated with the dataclasses.dataclass code generator.

>>> import dataclasses
>>> @dataclasses.dataclass
... class MyClass:
...     c: str
...     b: str
...     a: str = "Default"
...
>>> objdata = [
...     MyClass(c="Foo", b="Bar"),
...     MyClass(c="Test", b="Test"),
...     MyClass(c="asdf", b="yxcv")
...     ]
>>>
>>> for obj in objdata:
...     print(dataclasses.asdict(obj))
...
{'c': 'Foo', 'b': 'Bar', 'a': 'Default'}
{'c': 'Test', 'b': 'Test', 'a': 'Default'}
{'c': 'asdf', 'b': 'yxcv', 'a': 'Default'}

Note, this is implemented by using dataclasses.fields(obj), which ultimately just access the __dataclass_fields__ class variable. The details are all pretty easy to follow in the source code

Note, if you simply wanted to write every object that you know is of some specific dataclass type to a csv, I wouldn't create intermediate dicts. You could just do:

import operator
import csv

fields = [f.name for f in dataclasses.fields(MyClass)]
getter_func = operator.attrgetter(*fields)

with open("data.csv") as f:
    writer = csv.writer(f)
    writer.writerow(fields) # If you want a header
    writer.writerows(map(getter_func, objdata))

huangapple
  • 本文由 发表于 2023年6月5日 14:52:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76404084.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定