#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
было бы еще одним способом добиться такого поведения?