You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用类型对象调用带@validate_arguments装饰器的数据类构造器问题

问题分析与解决方案

问题描述

我有一个继承自抽象类的数据类,抽象类实现了样板代码,同时使用@validate_arguments装饰器在对象创建时自动将字符串转换为数值,部分字段在__post_init__中计算。但通过ReportEditor类接收Report子类类型并实例化时,装饰器的验证步骤未执行,导致字符串未转换而报错;直接调用子类构造器则正常工作。

代码复现

report.py

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pydantic import validate_arguments


@dataclass
class Report(ABC):
    def __post_init__(self):
        self.process_attributes()

    @abstractmethod
    def process_attributes(self):
        pass


@validate_arguments
@dataclass
class SpecificReport(Report):
    some_number: int
    some_other_number: float
    calculated_field: float = field(init=False)

    def process_attributes(self):
        self.calculated_field = self.some_number * self.some_other_number

report_editor.py

from inspect import signature

from report import Report, SpecificReport


class ReportEditor:
    def __init__(self, report_type: type[Report], content=None):
        self.content = content
        self.report_type = report_type
        self.explicit_fields = list(signature(report_type).parameters.keys())

    def process(self):
        initializable_dict = {key: val for key, val in self.content.items() if key in self.explicit_fields}
        report = self.report_type(**initializable_dict)
        print(report)

main.py

from report import SpecificReport
from report_editor import ReportEditor


def example():
    new_report = SpecificReport(1, 1.0)
    report_editor = ReportEditor(type(new_report), {
            "some_number": "1",
            "some_other_number": "1.0",
            "calculated_field": "1.0"
        })
    report_editor.process()


if __name__ == '__main__':
    example()

原因解析

  1. 装饰器作用对象问题:@validate_arguments装饰的是数据类自动生成的构造函数,而非类本身。直接调用SpecificReport(...)时,实际调用的是被装饰后的构造函数;但通过self.report_type(**dict)调用时,本质是调用类的原生__init__方法,并没有经过装饰器的包装逻辑,因此类型转换不生效。
  2. 父类加装饰器报错原因:抽象类Report无法直接实例化,@validate_arguments会试图包装其构造函数,但抽象类的构造逻辑与装饰器的实例化逻辑冲突,导致TypeError: cannot create 'cython_function_or_method' instances。

解决方案

方案一:改用Pydantic DataClass(推荐)

Pydantic的dataclass自带类型验证和自动转换功能,无需额外使用@validate_arguments,且能完美支持继承场景。修改report.py:

from abc import ABC, abstractmethod
from pydantic.dataclasses import dataclass, field


@dataclass
class Report(ABC):
    def __post_init__(self):
        self.process_attributes()

    @abstractmethod
    def process_attributes(self):
        pass


@dataclass
class SpecificReport(Report):
    some_number: int
    some_other_number: float
    calculated_field: float = field(init=False)

    def process_attributes(self):
        self.calculated_field = self.some_number * self.some_other_number

此时ReportEditor无需修改,直接调用self.report_type(**initializable_dict)即可自动完成字符串到数值的转换,且验证逻辑生效。

方案二:手动调用装饰后的构造函数

如果坚持使用标准库dataclass,可将装饰后的构造函数保存为类属性,在ReportEditor中手动调用:

  1. 修改report.py,为子类添加验证后的构造函数属性:
@dataclass
class SpecificReport(Report):
    some_number: int
    some_other_number: float
    calculated_field: float = field(init=False)

    def process_attributes(self):
        self.calculated_field = self.some_number * self.some_other_number

# 装饰__init__并保存为类属性
SpecificReport.__validated_init__ = validate_arguments(SpecificReport.__init__)
  1. 修改ReportEditor的process方法:
def process(self):
    initializable_dict = {key: val for key, val in self.content.items() if key in self.explicit_fields}
    # 先创建空实例,再调用验证后的构造函数,最后执行post_init
    report = object.__new__(self.report_type)
    self.report_type.__validated_init__(report, **initializable_dict)
    report.__post_init__()
    print(report)

方案三:统一实例化入口(抽象类添加类方法)

在抽象类Report中定义一个类方法作为实例化入口,子类继承后自动应用验证逻辑:

@dataclass
class Report(ABC):
    def __post_init__(self):
        self.process_attributes()

    @abstractmethod
    def process_attributes(self):
        pass

    @classmethod
    def create(cls, **kwargs):
        # 子类需确保__init__被@validate_arguments装饰
        return cls(**kwargs)

然后ReportEditor的process方法改为调用create类方法:

def process(self):
    initializable_dict = {key: val for key, val in self.content.items() if key in self.explicit_fields}
    report = self.report_type.create(**initializable_dict)
    print(report)

注意:此方案需要每个子类的__init__都被@validate_arguments装饰,或者在create方法中统一应用装饰器(需处理子类构造函数的动态装饰)。


内容的提问来源于stack exchange,提问作者lunchdilemma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 12:40:42