我有一个数据类,它继承了一个抽象类,这个抽象类实现了一些样板,并且还使用@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
?我遗漏了什么?或者这是不可能的(可能是泛型)?
1条答案
按热度按时间iklwldmw1#
这里是根本问题:
这是因为
pydantic.validate_arguments
装饰器返回了一个cythonized函数:.__annotations__
的一个可调用对象)。编辑:
但是,如果您确实需要验证,可以使用
pydantic.dataclasses
,它是标准库dataclasses
的"直接"(不完全是直接,但非常接近,他们在兼容性方面做了真正的努力)替代品。一些微妙之处:
__post_init__
中,参数还没有被解析和验证,但是如果你想验证/解析它们,你可以使用__post_init_post_parse__
。我们这样做了,否则self.some_number * self.some_other_number
将引发TypeError
dataclasses.InitVar
与dataclasses.field(init=False)
一起使用,因为如果没有InitVar
,则如果__post_init__
未设置calculated_field
,则验证将失败(所以我们不能使用__post_init_post_parse__
中的解析字段,因为丢失的属性在 * 之前 * 被检查过)。可能有一种方法可以阻止它强制执行,但这是我目前找到的。我不太适应。希望有人能找到更好的方法。__post_init_post_parse__
和process
中使用*args, **kwargs
,因为InitVar
将传递参数,因此此类的扩展程序可能也希望这样做,因此使其成为泛型。我不得不将
**args, **kwargs
添加到