英文:
Is there a Pydantic convention for a Model attribute that will default to a function of another attribute?
问题
我想定义一个具有以下属性的Pydantic BaseModel
:
- 两个
int
属性a
和b
。 a
是一个必填属性。b
是可选的,如果没有设置,将默认为a+1
。
有没有办法实现这个?这是我的尝试。
from typing import Optional
from pydantic import BaseModel, validator
class A(BaseModel):
a: int
b: Optional[int] = None # 希望如果没有设置默认为 a+1
@validator('b', always=True)
def default_b_value(cls, b, values):
if b is None and 'a' in values:
return values['a']+1
return b
我对这个的问题是它不符合A
的保证。对于实际构造的任何A
,b
永远不会是 None
,因此它的类型应该是 int
,而不是 Optional[int]
。
英文:
I want to define a Pydantic BaseModel
with the following properties:
- Two
int
attributesa
andb
. a
is a required attributeb
is optional, and will default toa+1
if not set.
Is there a way to achieve this? This is what I've tried.
from typing import Optional
from pydantic import BaseModel, validator
class A(BaseModel):
a: int
b: Optional[int] = None # want to default to a+1 if not set
@validator('b', always=True)
def default_b_value(cls, b, values):
if b is None and 'a' in values:
return values['a']+1
return b
My problem with this is it doesn't match the guarantees of A
. For any A
that's actually constructed, b
will never be None
so its type should be int
, not Optional[int]
.
答案1
得分: 3
I would not presume to know about conventions for this, but to achieve the behavior you want, you have a few options.
One way is to define b
as being of the type int
and setting a default_factory
to return a sentinel value, then identity-check for that value in the validator, similar to what you did with None
. I suppose the cleanest way (in the type safety sense) would be to subclass int
just for that purpose to have an object that is actually distinct while still being of the correct type.
from typing import Any, cast
from pydantic import BaseModel, Field, validator
class _SentinelInt(int):
pass
_sentinel_int = _SentinelInt()
class Foo(BaseModel):
a: int
b: int = Field(default_factory=lambda: _sentinel_int)
@validator("b", always=True)
def default_b_value(cls, v: int, values: dict[str, Any]) -> int:
if v is _sentinel_int:
return cast(int, values["a"] + 1)
return v
Side note: You can be sure that the a
value is present in the values
dictionary because validation occurs in the order that fields are defined.
Demo:
foo1 = Foo(a=42)
foo2 = Foo(a=-1, b=69)
print(foo1.json(indent=4))
print(foo2.json(indent=4))
Output:
{
"a": 42,
"b": 43
}
{
"a": -1,
"b": 69
}
An arguably less clean way would be to simply stick with None
as the default but still type b
as int
and simply set a specific type: ignore
directive for the default assignment:
from typing import Any, cast
from pydantic import BaseModel, validator
class Foo(BaseModel):
a: int
b: int = None # type: ignore[assignment]
@validator("b", always=True)
def default_b_value(cls, v: int, values: dict[str, Any]) -> int:
if v is None:
return cast(int, values["a"] + 1)
return v
From the perspective of the type checker and for all intents and purposes at runtime the value of the b
field of any instance will always be an int
, so I would say this approach is well justified. Even though it may not be strictly type safe, the intent is still crystal clear.
英文:
I would not presume to know about conventions for this, but to achieve the behavior you want, you have a few options.
One way is to define b
as being of the type int
and setting a default_factory
to return a sentinel value, then identity-check for that value in the validator, similar to what you did with None
. I suppose the cleanest way (in the type safety sense) would be to subclass int
just for that purpose to have an object that is actually distinct while still being of the correct type.
from typing import Any, cast
from pydantic import BaseModel, Field, validator
class _SentinelInt(int):
pass
_sentinel_int = _SentinelInt()
class Foo(BaseModel):
a: int
b: int = Field(default_factory=lambda: _sentinel_int)
@validator("b", always=True)
def default_b_value(cls, v: int, values: dict[str, Any]) -> int:
if v is _sentinel_int:
return cast(int, values["a"] + 1)
return v
Side note: You can be sure that the a
value is present in the values
dictionary because validation occurs in the order that fields are defined.
Demo:
foo1 = Foo(a=42)
foo2 = Foo(a=-1, b=69)
print(foo1.json(indent=4))
print(foo2.json(indent=4))
Output:
{
"a": 42,
"b": 43
}
{
"a": -1,
"b": 69
}
An arguably less clean way would be to simply stick with None
as the default but still type b
as int
and simply set a specific type: ignore
directive for the default assignment:
from typing import Any, cast
from pydantic import BaseModel, validator
class Foo(BaseModel):
a: int
b: int = None # type: ignore[assignment]
@validator("b", always=True)
def default_b_value(cls, v: int, values: dict[str, Any]) -> int:
if v is None:
return cast(int, values["a"] + 1)
return v
From the perspective of the type checker and for all intents and purposes at runtime the value of the b
field of any instance will always be an int
, so I would say this approach is well justified. Even though it may not be strictly type safe, the intent is still crystal clear.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论