Когда создаются экземпляры вложенных моделей по умолчанию?

#python #pydantic

Вопрос:

У меня сложная иерархия дерева конфигураций, в которой модели pydantic вложены в несколько слоев, и я заметил странное поведение, которое я не могу объяснить.

Давайте рассмотрим этот пример на основе документации:

 from pydantic import BaseModel, validator
from datetime import datetime
from time import sleep

class DemoModel(BaseModel):
    ts: datetime = None

    @validator('ts', pre=True, always=True)
    def set_ts_now(cls, v):
        return v or datetime.now()

    def __init__(self, **data):
        super().__init__(**data)
        print('Creating new DemoModel with ts =', self.ts)
 

Это работает так, как и ожидалось:

 d1 = DemoModel()
sleep(1)
d2 = DemoModel()

assert d1.ts < d2.ts
 

Теперь, когда я вложил это в рекурсивную модель со значением по умолчанию, подобным этому:

 class CompositeModel(BaseModel):
    demo: DemoModel = DemoModel()
 

Затем DemoModel экземпляр создается только один раз, когда CompositeModel класс определен.
Следовательно:

 c1 = CompositeModel()
sleep(1)
c2 = CompositeModel()

assert c1 == c2
 

Это немного неожиданно, и я не видел, чтобы это упоминалось в документации, но это имеет некоторый смысл, поскольку мы имеем дело с переменными класса, которые в обычном python создаются для каждого класса, а не для каждого объекта.

Однако не было бы поэтому c1.demo и c2.demo тем же объектом? Таким образом, если я изменю один, я ожидал бы, что «другой» тоже изменится, но это не так:

 c1.demo.ts = datetime(2042,1,1)
assert c1.demo.ts == c2.demo.ts # fails!
 

Так что совершенно очевидно, что это разные примеры. Но конструктор ( __init__ ) был вызван только один раз! Что здесь происходит?


Если я определяю составную модель с помощью валидатора, который динамически создает экземпляры подмодели, я возвращаю исходное поведение, где каждый экземпляр родительского элемента также получает новый экземпляр дочернего элемента:

 class CompositeModel2(BaseModel):
    demo: DemoModel = None

    @validator('demo', pre=True, always=True)
    def set_demo_dynamically(cls, v):
        return v or DemoModel()
 

Может быть, использование a Field с a dynamic_factory было бы еще одним способом добиться такого поведения?