python-3.x 使用仅给定类型对象的装饰器调用数据类构造函数

x0fgdtte  于 2023-01-27  发布在  Python
关注(0)|答案(1)|浏览(157)

我有一个数据类,它继承了一个抽象类,这个抽象类实现了一些样板,并且还使用@validate_arguments装饰器在对象创建时立即将字符串转换回数字。这个数据类是一系列数字,其中一些是在__post_init__中计算的。
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类型的类初始化,在创建时收集关于该类的一些元数据,然后有一些方法对这些对象执行操作,包括获取一些内容,然后从字典中构造这种类型的新对象。我们确定哪些字段用inspect.signature显式设置,展开字典并调用构造函数。
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)

但是,在命中process_attributes时会产生一个错误,因为validate_arguments步骤没有执行,除此之外,对象的初始化如我所期望的那样,但是由于值是字符串,它们保持原样,并且只有在尝试执行操作时才会抛出异常。
这样做效果很好,并产生所需的行为:

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

但是当然,其目的是将其抽象化,并且允许这个ReportEditor类能够在不知道它是什么类型的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()

我试着把@validate_arguments放在父类和子类上,也试着只放在Report父类上,结果都是TypeError: cannot create 'cython_function_or_method' instances,我找不到任何其他方法可以只使用type对象从外部调用构造函数。
为什么在这个示例中构造函数被正确调用了,而装饰器函数却没有被正确调用?为了得到完整的构造函数,有没有可能将type对象强制转换为Callable?我遗漏了什么?或者这是不可能的(可能是泛型)?

iklwldmw

iklwldmw1#

这里是根本问题:

In [1]: import report

In [2]: new_report = report.SpecificReport(1, 1.0)

In [3]: type(new_report) is report.SpecificReport
Out[3]: False

这是因为pydantic.validate_arguments装饰器返回了一个cythonized函数:

In [4]: report.SpecificReport
Out[4]: <cyfunction SpecificReport at 0x1103bb370>
  • 函数 * 负责验证,类构造函数不负责,看起来这个装饰器是实验性的,至少现在,它不是为类设计的(它只是碰巧工作,因为类毕竟只是.__annotations__的一个可调用对象)。

编辑:
但是,如果您确实需要验证,可以使用pydantic.dataclasses,它是标准库dataclasses的"直接"(不完全是直接,但非常接近,他们在兼容性方面做了真正的努力)替代品。

from abc import ABC, abstractmethod
import dataclasses
import pydantic

@pydantic.dataclasses.dataclass
class Report(ABC):
    def __post_init_post_parse__(self, *args, **kwargs):
        self.process_attributes()

    @abstractmethod
    def process_attributes(self, *args, **kwargs):
        pass

@pydantic.dataclasses.dataclass
class SpecificReport(Report):
    some_number: int
    some_other_number: float
    calculated_field: dataclasses.InitVar[float] = dataclasses.field(init=False)

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

一些微妙之处:

  • __post_init__中,参数还没有被解析和验证,但是如果你想验证/解析它们,你可以使用__post_init_post_parse__。我们这样做了,否则self.some_number * self.some_other_number将引发TypeError
  • 必须将dataclasses.InitVardataclasses.field(init=False)一起使用,因为如果没有InitVar,则如果__post_init__未设置calculated_field,则验证将失败(所以我们不能使用__post_init_post_parse__中的解析字段,因为丢失的属性在 * 之前 * 被检查过)。可能有一种方法可以阻止它强制执行,但这是我目前找到的。我不太适应。希望有人能找到更好的方法。
  • 必须在__post_init_post_parse__process中使用*args, **kwargs,因为InitVar将传递参数,因此此类的扩展程序可能也希望这样做,因此使其成为泛型。

我不得不将**args, **kwargs添加到

相关问题