【问题标题】:How to initialize a Pydantic object from field values given by position instead of name?如何从位置而不是名称给出的字段值初始化 Pydantic 对象?
【发布时间】:2021-12-11 03:59:29
【问题描述】:

我无法找到一种简单的方法来从位置给定的字段值(例如在列表而不是字典中)初始化 Pydantic 对象,因此我编写了类方法 positional_fields() 来创建所需的字典一个可迭代的:

from typing import Optional, Iterable, Any, Dict
from pydantic import BaseModel


class StaticRoute(BaseModel):
    if_name: str
    dest_ip: str
    mask: str
    gateway_ip: str
    distance: Optional[int]
    
    @classmethod
    def positional_fields(cls, values: Iterable) -> Dict[str, Any]:
        return dict(zip(cls.__fields__, values))


input_lines = """
  route ab 10.0.0.0 255.0.0.0 10.220.196.23 1
  route gh 10.0.2.61 255.255.255.255 10.220.198.38 1
""".splitlines()

for line in input_lines:
    words = line.split()
    if words and words[0] == 'route':
        sroute = StaticRoute(**StaticRoute.positional_fields(words[1:]))
        print(sroute)
if_name='ab' dest_ip='10.0.0.0' mask='255.0.0.0' gateway_ip='10.220.196.23' distance=1
if_name='gh' dest_ip='10.0.2.61' mask='255.255.255.255' gateway_ip='10.220.198.38' distance=1

有没有更直接的方法来实现这一点?

我的方法期望__fields__ 字典按照字段在类中定义的顺序具有键。我不确定这是否得到保证(假设 Python 3.6+)。

【问题讨论】:

  • 我已将示例代码更改为包含示例数据的完整工作代码。

标签: python iterable pydantic


【解决方案1】:

改用dataclasses 怎么样?比如:

from typing import Optional

from pydantic.dataclasses import dataclass


@dataclass
class StaticRoute:
    if_name: str
    dest_ip: str
    mask: str
    gateway_ip: str
    distance: Optional[int]


words = "route if_name dest_ip mask gateway_ip 10".split()
print(StaticRoute(*words[1:])

# StaticRoute(if_name='if_name', dest_ip='dest_ip', mask='mask', gateway_ip='gateway_ip', distance=10)

【讨论】:

  • 现在我注意到它是来自 Pydantic 的 dataclass。我必须检查与文档中首选的 BaseModel 的所有区别。
【解决方案2】:

类方法BaseModel.parse_obj()返回一个由字典初始化的对象实例。我们可以创建一个类似的类方法parse_iterable(),它接受一个可迭代对象。

from typing import Optional, Iterable, Any, Dict
from pydantic import BaseModel


class BaseModelExt(BaseModel):
    @classmethod
    def parse_iterable(cls, values: Iterable):
        return cls.parse_obj(dict(zip(cls.__fields__, values)))

    
class StaticRoute(BaseModelExt):
    if_name: str
    dest_ip: str
    mask: str
    gateway_ip: str
    distance: Optional[int]


input_lines = """
  route ab 10.0.0.0 255.0.0.0 10.220.196.23 1
  route gh 10.0.2.61 255.255.255.255 10.220.198.38 1
""".splitlines()

for line in input_lines:
    words = line.split()
    if words and words[0] == 'route':
        sroute = StaticRoute.parse_iterable(words[1:])
        print(sroute)

注意:如果BaseModel.__fields__ 的订单得到保证,我们仍然缺少确认。

【讨论】:

  • 请注意,如果迭代中的项目数大于模型中的字段数,这将无法验证
  • @whatthe 不幸的是它不会失败。压缩不同长度的迭代器会产生最短长度的迭代器并忽略过多的项目。 (list(zip((1,2),(5,6,7))) == [(1,5),(2,6)]) --- 在 Python 3.10+ 中,我们可以将 strict=True 参数添加到 zip() 以在迭代长度不相等时使其失败,但这会使 StaticRoute.distance 属性不可选。我们需要实现我们自己的 zip() 变体,具有非对称严格性,以允许可选属性并因过多项目而失败。
猜你喜欢
  • 1970-01-01
  • 2021-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-17
  • 1970-01-01
  • 1970-01-01
  • 2021-07-05
相关资源
最近更新 更多