我想创建一个数据类基类,其中子类中的所有字段都自动为 Optional 并且默认为 None (如果没有提供默认值)。

下面的代码......似乎几乎可以做我想要的,但不完全是。它出错的方式与我从未编写过 __init_subclass__ 的方式相同(即提示未填充的参数)……也许是因为我的代码在数据类魔法发生后运行?

@dataclass(order=True, frozen=True)
class BaseDictKey:
    def __init_subclass__(cls, *args, **kwargs):
        super().__init_subclass__(*args, **kwargs)
        # noinspection PyUnresolvedReferences
        for field in cls.__dataclass_fields__.values():
            field.default = None if field.default is None else field.default
            field.type = typing.Union[field.type, NoneType]

@dataclass(order=True, frozen=True)
class ScoreDictKey(BaseDictKey):
    catalog: str  # magically becomes catalog: Optional[str] = None
    dataset: str = 'foo'  # magically becomes dataset: Optional[str] = 'foo'


(如果你想知道为什么我想要这个,我有另一个基类,它使用这些 BaseDictKeys,它期望子类中的任何和所有字段都是 Optional。我想如果我检测到某些东西不是 Optional,我可以改为引发异常,但这似乎更丑。)

这在 Python 3.7+ 中可能吗?

最佳答案

我找到了一种修改类 __annotations__ 字段以使字段可选并直接在类上设置属性以提供默认值 None 的方法:

from dataclasses import dataclass

import typing


@dataclass(order=True, frozen=True)
class BaseDictKey:
    def __init_subclass__(cls, *args, **kwargs):
        for field, value in cls.__annotations__.items():
            cls.__annotations__[field] = typing.Union[value, None]
            if not hasattr(cls, field):
                setattr(cls, field, None)
        super().__init_subclass__(*args, **kwargs)


@dataclass(order=True, frozen=True)
class ScoreDictKey(BaseDictKey):
    catalog: str  # magically becomes catalog: Optional[str] = None
    dataset: str = 'foo'  # magically becomes dataset: Optional[str] = 'foo'

    def __init__(self, *args, **kwargs):  # to get rid of PyCharm warning
        pass


c = ScoreDictKey()
print(c.catalog, c.dataset)  # None foo

关于Python 数据类 : can you set a default default for fields?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58554092/

10-12 20:59