如何在不重复的情况下访问Pydantic对象的嵌套可空值?



我从API收到一个这样的结构:

from pydantic import BaseModel
class Country(BaseModel):
code: Optional[str] = None
class Address(BaseModel):
street: str
country: Optional[Country] = None
class Person(BaseModel):
firstName: str
lastName: str
address: Optional[Address] = None

如果我想获得国家代码,我需要这样做:

def get_country_code(p: Person):
if p.address and p.adress.country:
return p.address.country.code
return None

正如你所看到的,当深度为3时,我需要复制p.address3次。在我的实际情况中,数据甚至更嵌套。

这是超级烦人的,使代码更难阅读。我希望有这样的东西:

def get_country_code(p: Person):
return p.?address.?country.code

属性访问前的?本质上表示"如果存在以下属性,则取其。如果没有,返回None">

可能的解决方案#1:转换成字典如果它是一个字典,我至少可以这样做:

def get_country_code(p: Person):
return p.get("address", {}).get("country", {}).get("code", None)

因此,我目前正在考虑将其转换回字典…这有点可悲。是否有更好的方法来访问嵌套属性?

可能的解决方案#2:字典默认值+修改BaseModel

我可以将Optional[Address]更改为Union[Address, Dict[str, Any]],并使默认值为空字典。然而,Pydantic BaseModel没有.get(some_str)。因此我需要修改它。

我无法改变这样一个事实:数据结构嵌套得如此之深,或者有许多可空字段。我只好接受了。

有没有办法有.get("attribute", "fallback")语法而不失去编辑器/我的支持?

我通常只使用getattr。它不是很漂亮,但它的工作

return getattr(getattr(p.address, 'country', None), 'code', None)

基于@Matias Cicero的回答,如果你的数据结构像你说的那样是重度嵌套的,也许像这样的东西会变得更可读。但是我甚至会猜测,一些整洁的库已经包含了一个方法来迭代地应用相同的函数,就像这样。

attrs = ["address", "country", "code"]
def get_country_code(p: Person):
i = 0
while p := getattr(p, attrs[i], None) and i < len(attrs):
i += 1
return p

如果可以使用第三方模块,我建议使用pydash:

import pydash as _
...
def get_country_code(p: Person):
return _.get(p, 'address.country.code')