英文:
How to define a Pydantic model nested under a class
问题
以下是您提供的代码的翻译部分:
我有两个Pydantic模型:
from typing import List, Union
from pydantic import BaseModel
class Students:
class Student(BaseModel):
StudentName: str
StudentAge: int
class StudentRequest(BaseModel):
Class: int
UUID: str
Students: Union[List[Student], None]
对于上面的代码中的Students: Union[List[Student], None]
部分,我收到错误消息Unresolved reference 'Student'
。我们不能在一个类下定义模型并在其中使用吗?
下面的代码可以工作,但我想了解上面的BaseModel
嵌套在一个类下是否能工作:
class Student(BaseModel):
StudentName: str
StudentAge: int
class StudentRequest(BaseModel):
Class: int
UUID: str
Students: Union[List[Student], None]
希望这对您有所帮助。
<details>
<summary>英文:</summary>
I have two Pydantic models:
```python
from typing import List, Union
from pydantic import BaseModel
class Students:
class Student(BaseModel):
StudentName: str
StudentAge: int
class StudentRequest(BaseModel):
Class: int
UUID: str
Students: Union[List[Student], None]
For the above class at Students: Union[List[Student], None]
, I get the error Unresolved reference 'Student'
. Can we not define a model under a class and use it for segregating them?
The code below works, but I want to get an understanding whether the above BaseModel
nested under a class will work or not:
class Student(BaseModel):
StudentName: str
StudentAge: int
class StudentRequest(BaseModel):
Class: int
UUID: str
Students: Union[List[Student], None]
答案1
得分: 4
你需要理解,只要外部类没有完全构建(当您仍然在其命名空间内设置东西时),您将不可避免地需要处理前向引用。
所以在执行此操作时,有两个强制性的事情(和一个可选的事情)你需要记住。
1) 使用限定的类名 OuterClass.InnerClass
Python 解释器本身对在注释中引用另一个内部类的前向引用不会有任何问题。这仅仅是因为它默认情况下实际上不会对这些注释执行任何操作。所以你可以像这样做:
from pydantic import BaseModel
class OuterClass:
class Student(BaseModel):
name: str
age: int
class StudentRequest(BaseModel):
...
students: list["Student"]
但是这在 Pydantic 模型中会失败,因为它们实际上使用这些注释来构建基于它们的对象。正如您将在下一部分中看到的,Pydantic 在某个时候将不得不实际解析对 Student
的引用,以在运行时获取实际的基础类。而且由于这将不可避免地发生在OuterClass
的作用域之外,如果没有限定名称,它将遇到NameError
。
因此,您必须像这样执行:
...
class OuterClass:
class Student(BaseModel):
...
class StudentRequest(BaseModel):
...
students: list["OuterClass.Student"]
2) 在外部类构建完成后更新前向引用
如上所示的注释在内部存储为ForwardRef
对象。
如上所述,Pydantic 最终将不得不解析这些前向引用,以便您能够实际使用这些模型。
但是它不总能够自动解析。引用文档中的一段话:
> 在某些情况下,模型创建过程中无法解析ForwardRef
。[...] 当发生这种情况时,您需要在模型创建后调用update_forward_refs
,然后才能使用它。
但是在像您的设置中,当模型嵌套在外部类的命名空间中时,您不能只是在模型创建后这样做。您必须在外部类创建后才能执行。
因此,在这种设置下,您必须执行以下操作:
from pydantic import BaseModel
class OuterClass:
class Student(BaseModel):
name: str
age: int
class StudentRequest(BaseModel):
...
students: list["OuterClass.Student"]
OuterClass.StudentRequest.update_forward_refs()
请注意,这个调用是在创建OuterClass
之后外部发生的。
3) 启用注释的延迟评估(可选)
自从PEP 563以来,您可以在模块顶部执行from __future__ import annotations
,然后省略前向引用中的引号。这只是提高了可读性,使编码变得更加简单。
因此,总体而言,您的代码应该像这样:
from __future__ import annotations
from pydantic import BaseModel
class OuterClass:
class Student(BaseModel):
name: str
age: int
class StudentRequest(BaseModel):
...
students: list[OuterClass.Student]
OuterClass.StudentRequest.update_forward_refs()
演示:
print(OuterClass.StudentRequest.schema_json(indent=4))
obj = OuterClass.StudentRequest.parse_obj({
"students": [
{"name": "foo", "age": 18},
{"name": "bar", "age": 19},
]
})
print(obj.json(indent=4))
输出:
{
"title": "StudentRequest",
"type": "object",
"properties": {
"students": {
"title": "Students",
"type": "array",
"items": {
"$ref": "#/definitions/Student"
}
}
},
"required": [
"students"
],
"definitions": {
"Student": {
"title": "Student",
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"age": {
"title": "Age",
"type": "integer"
}
},
"required": [
"name",
"age"
]
}
}
}
{
"students": [
{
"name": "foo",
"age": 18
},
{
"name": "bar",
"age": 19
}
]
}
英文:
You need to understand that as long as the outer class is not fully constructed (when you are still setting up things inside its namespace), you will inevitably have to deal with forward references.
So there are two mandatory things (and one optional) you need to remember, when doing this.
1) Use the qualified class name OuterClass.InnerClass
The Python interpreter itself will have no trouble with a forward reference to another inner class in an annotation. That is simply because it does not actually do anything with those annotations by default. So you could just do this:
from pydantic import BaseModel
class OuterClass:
class Student(BaseModel):
name: str
age: int
class StudentRequest(BaseModel):
...
students: list["Student"]
But this will fall apart with Pydantic models because those actually use those annotations to construct objects based off of them. As you will see in the next section, at some point Pydantic will have to actually resolve the refernce to Student
so get the actual underlying class at runtime. And since that will inevitably happen outside the scope of the OuterClass
, without the qualified name, it will run into a NameError
.
So you have to do it like this:
...
class OuterClass:
class Student(BaseModel):
...
class StudentRequest(BaseModel):
...
students: list["OuterClass.Student"]
2) Update forward references after the outer class is constructed
An annotation as shown above is internally stored as a ForwardRef
object.
As mentioned above, Pydantic will have to resolve those forward references eventually, for you to be able to actually use those models.
However it is not always able to do so automatically. To quote the documentation:
> In some cases, a ForwardRef
won't be able to be resolved during model creation. [...] When this happens, you'll need to call update_forward_refs
after the model has been created before it can be used.
But with a setup like yours, when the model is nested in the namespace of an outer class, you cannot just do so after the model is created. You must do that after the outer class is created.
So with that setup, you will have to do this:
from pydantic import BaseModel
class OuterClass:
class Student(BaseModel):
name: str
age: int
class StudentRequest(BaseModel):
...
students: list["OuterClass.Student"]
OuterClass.StudentRequest.update_forward_refs()
Notice that the call happens outside of OuterClass
after it is created.
3) Enable postponed evaluation of annotations (optional)
Since PEP 563 you can do from __future__ import annotations
at the top of your module and then omit the quotes from your forward references. This just improves readability and makes things generally easier to code.
So in total, your code should look like this:
from __future__ import annotations
from pydantic import BaseModel
class OuterClass:
class Student(BaseModel):
name: str
age: int
class StudentRequest(BaseModel):
...
students: list[OuterClass.Student]
OuterClass.StudentRequest.update_forward_refs()
Demo:
print(OuterClass.StudentRequest.schema_json(indent=4))
obj = OuterClass.StudentRequest.parse_obj({
"students": [
{"name": "foo", "age": 18},
{"name": "bar", "age": 19},
]
})
print(obj.json(indent=4))
Output:
{
"title": "StudentRequest",
"type": "object",
"properties": {
"students": {
"title": "Students",
"type": "array",
"items": {
"$ref": "#/definitions/Student"
}
}
},
"required": [
"students"
],
"definitions": {
"Student": {
"title": "Student",
"type": "object",
"properties": {
"name": {
"title": "Name",
"type": "string"
},
"age": {
"title": "Age",
"type": "integer"
}
},
"required": [
"name",
"age"
]
}
}
}
{
"students": [
{
"name": "foo",
"age": 18
},
{
"name": "bar",
"age": 19
}
]
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论