Bor*_*lis 6 python inheritance init pydantic
我正在尝试将 pydantic 中的继承与自定义 __init__ 函数一起使用。我有父类(鱼)和子类(鲨鱼),它们都需要更多的初始化而不仅仅是设置字段(在 MWE 中由附加的打印语句表示)。所以我需要重写他们的初始化。
我试过:
class fish(BaseModel):
    name: str
    def __init__(self, name):
        super().__init__(name=name)
        print("Fish initialization successful!")
        
class shark(fish):
    color: str
    def __init__(self, name, color):
        super().__init__(name=name)
        self.color=color
        print("Shark initialization successful!")
        
f = fish(name="nemo")
print(f)
s = shark(name="bruce", color="grey")
但这会引发验证错误:
Fish initialization successful!
name='nemo'
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[149], line 17
     15 f = fish(name="nemo")
     16 print(f)
---> 17 s = shark(name="bruce", color="grey")
Cell In[149], line 11, in shark.__init__(self, name, color)
     10 def __init__(self, name, color):
---> 11     super().__init__(name=name)
     12     self.color=color
     13     print("Shark initialization successful!")
Cell In[149], line 4, in fish.__init__(self, name)
      3 def __init__(self, name):
----> 4     super().__init__(name=name)
      5     print("Fish initialization successful!")
File ~/Desktop/treeline_wt/1588-yieldmodeling-integration/device-predictions/.venv/lib/python3.9/site-packages/pydantic/main.py:341, in pydantic.main.BaseModel.__init__()
ValidationError: 1 validation error for shark
color
  field required (type=value_error.missing)
我从同事那里得到的有效解决方案是:
class fish(BaseModel):
    name: str
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        print("Fish initialization successful!")
        
class shark(fish):
    color: str
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        print("Shark initialization successful!")
        
# f = fish(name="nemo")
# print(f)
s = shark(name="bruce", color="grey")
经过检查,只有当鱼super().__init__收到color关键字时才有效,即更改它以super().__init__(name=kwargs['name'])引发相同的验证错误。这让我感到困惑,我不明白为什么 Fish 类需要了解其子类的属性。我怎么理解这一点?
Fish这与需要了解有关 上定义的字段的任何信息无关Shark。它与了解BaseModel.__init__任何给定模型具有哪些字段以及根据这些字段验证所有关键字参数有关。
您需要记住,在类创建期间,即在初始化它的任何特定实例之前,任何模型类都会在“幕后”发生很多事情。元类负责此操作。
本质上,您需要Shark像这样考虑定义过程:
Shark读取类名称空间。color: str)。name来自)。FishShark(加上验证器和一堆其他东西)都已完全构建。它现在有字段name和color。该BaseModel.__init__方法将始终查看给定模型上定义的所有字段,并根据这些字段验证提供的关键字参数。
super().__init__(name=name)当您从内部调用时Shark.__init__,您基本上是在调用Fish.__init__(self, name=name),即您将(未初始化的)Shark实例self以及name参数传递给Fish.__init__。
然后从内部Fish.__init__您再次执行super().__init__(name=name),这意味着您正在调用BaseModel.__init__(self, name=name)并再次仅将未完成的Shark实例和关键字参数传递name给它。(记住self仍然是那个Shark对象。)
但是,BaseModel.__init__查看Shark它获得的实例,会发现该类为其定义Shark了两个字段(name和color),并且它们都不是可选的/没有默认值。它将看到您只提供了name关键字参数,但未能提供color。因此会引发相应的ValidationError.
您随后手动尝试分配的事实self.color = color并不重要,因为甚至从未到达该行。
这就是为什么您必须始终将所有与字段相关的关键字参数“沿着链向上”传递到BaseModel.__init__. 这就是为什么第二个代码片段中的代码可以正常工作的原因。
乍一看这似乎不直观,但 Pydantic 模型并不是简单的数据类。幕后发生了很多事情,这带来了一些限制,不幸的是,这些限制有时没有记录在案(就像在本例中),只有在您真正深入研究源代码后才会变得清晰。