如何在数据类模块的 asdict 函数中使用枚举值

How to use enum value in asdict function from dataclasses module

我有一个数据类,其字段 template 类型为 Enum。使用 asdict 函数时,它将我的数据类转换为字典。是否可以使用 FoobarEnumvalue 属性来 return 字符串值而不是 Enum 对象?

我最初的想法是使用 asdict 函数的 dict_factory=dict 参数并提供我自己的工厂,但我不知道该怎么做。

from dataclasses import dataclass, asdict
from enum import Enum


@dataclass
class Foobar:
  name: str
  template: "FoobarEnum"


class FoobarEnum(Enum):
  FIRST = "foobar"
  SECOND = "baz"


foobar = Foobar(name="John", template=FoobarEnum.FIRST)

print(asdict(foobar))

当前输出:

{'name': 'John', 'template': <FoobarEnum.FIRST: 'foobar'>}

目标:

{'name': 'John', 'template': 'foobar'}

这不能用标准库来完成,除非通过一些我不知道的元类枚举 hack。 Enum.nameEnum.value 是内置的,不应更改。

使用数据类 default_factory 的方法也行不通。因为调用 default_factory 来为数据类成员生成默认值,而不是自定义对成员的访问。

您可以将 Enum 成员或 Enum.value 作为数据类成员,这就是 asdict() 将要 return 的情况。

如果您想保留一个 Enum 成员 - 而不仅仅是 Enum.value- 作为数据类成员,并且有一个函数将其转换为 return 的 Enum.value 字典枚举成员的正确方法是实现您自己的方法 return 数据类作为字典。

from dataclasses import dataclass
from enum import Enum


class FoobarEnum(Enum):
    FIRST = "foobar"
    SECOND = "baz"


@dataclass
class Foobar:
    name: str
    template: FoobarEnum

    def as_dict(self):
        return {
            'name': self.name,
            'template': self.template.value
        }


# Testing.
print(Foobar(name="John", template=FoobarEnum.FIRST).as_dict())
# {'name': 'John', 'template': 'foobar'}

你可以实现__deepcopy__方法来实现你想要的:

class FoobarEnum(Enum):
  FIRST = "foobar"
  SECOND = "baz"

  def __deepcopy__(self, memo):
      return self.value

asdict 函数处理数据类、元组、列表和字典。如果是任何其他类型,它会调用:

copy.deepcopy(obj)

像这样覆盖 __deepcopy__ 可能不是最好的主意。

像下面一样添加__post_init__

from dataclasses import dataclass, asdict
from enum import Enum
from typing import Union


@dataclass
class Foobar:
    name:str
    template: Union["FoobarEnum", str]
    def __post_init__(self):
        self.template = self.template.value

class FoobarEnum(Enum):
    FIRST = "foobar"
    SECOND = "baz"

foobar = Foobar(name="John", template=FoobarEnum.FIRST)
print(asdict(foobar))

根据用例,您可以从 strEnum 继承,或者只有一个初始化字段。

你试过吗?

import json

def dumps(object):
    def default(o):
        if isinstance(o, Enum):
            # use enum value when JSON deserialize the enum
            return o.__dict__['_value_'] 
        else:
            return o.__dict__
    return json.dumps(object, default=default)

print(json.dumps(YOUR_OBJECT_CONTAINS_ENUMS, default=default))
from dataclasses import dataclass, asdict
from enum import Enum


class FoobarEnum(Enum):
    FIRST = "foobar"
    SECOND = "baz"


@dataclass
class Foobar:
    name: str
    template: FoobarEnum


def my_dict(data):

    return {
        field: value.value if isinstance(value, Enum) else value
        for field, value in data
    }


foobar = Foobar(name="John", template=FoobarEnum.FIRST)

data = {'name': 'John', 'template': 'foobar'}

assert asdict(foobar, dict_factory=my_dict) == data

其实你可以做到的。 asdict 有关键字参数 dict_factory 允许你在那里处理你的数据:

from dataclasses import dataclass, asdict
from enum import Enum


@dataclass
class Foobar:
  name: str
  template: "FoobarEnum"


class FoobarEnum(Enum):
  FIRST = "foobar"
  SECOND = "baz"


def custom_asdict_factory(data):

    def convert_value(obj):
        if isinstance(obj, Enum):
            return obj.value
        return obj

    return dict((k, convert_value(v)) for k, v in data)


foobar = Foobar(name="John", template=FoobarEnum.FIRST)

print(asdict(foobar, dict_factory=custom_asdict_factory))
# {'name': 'John', 'template': 'foobar'}

我有一个类似的问题,我需要将我的数据 class 对象序列化为 JSON 并通过添加 str 作为第一个 class FoobarEnum 继承来解决它:

import json
from dataclasses import dataclass, asdict
from enum import Enum


@dataclass
class Foobar:
  name: str
  template: "FoobarEnum"


class FoobarEnum(str, Enum):
  FIRST = "foobar"
  SECOND = "baz"


foobar = Foobar(name="John", template=FoobarEnum.FIRST)

print(json.dumps(asdict(foobar)))

它不会改变 asdict 的行为,但我现在可以序列化该对象。

参考:Serialising an Enum member to JSON