Двухфазная инициализация



Иногда, по каким-то причинам мы не можем выполнить всю инициализацию при создании класса (в конструкторе или в __init__). Например, это может быть выполнение асинхронного ввода/вывода, простановка циклических ссылок между двумя созданными объектами или особоый механизм обработки ошибок инициализации. В этом случае иногда создают вспомогательный метод, который нужно вызвать сразу после создания объекта. Стоит использовать такой подход с осторожностью.



Например, мы хотим создать гейтвей для работы с БД. Следующий код не будет работать:



class SomeGW:

def __init__(self, db_uri):

self.connection = await asyncpg.connect(db_uri)



gw = SomeGw("postgresql://postgres@localhost/test")



Мы не можем выполнять async код в ините класса, поэтому можно попытаться сделать двухфазную инициализацию:



class SomeGW:

def __init__(self):

self.connection = None



async def connect(self, db_uri):

self.connection = await asyncpg.connect(db_uri)



gw = SomeGw()

await gw.connect("postgresql://postgres@localhost/test")



В таком случае необходимо следить, что соединение не будет использовано до завершения второй фазы инициализации (вызова connect). Также, у созданного объекта формально self.connection может быть None, что приведет к дополнительным проверкам в коде всех методов и предупреждениям линтера. Проще было ввести дополнительную функцию:



class SomeGW:

def __init__(self, connection):

self.connection = connection



async def new_some_gw(db_uri):

connection = await asyncpg.connect(db_uri)

return SomeGw(connection)



gw = await new_some_gw("postgresql://postgres@localhost/test")



Проблемы многофазной инициализации:

• Объект может быть инициализирован частично, что приведет к ошибкам выполнения

• Линтеры будут требовать дополнительных проверок в методах

• Корректная последовательность инициализации класса неочевидна из его API. Ситуация становится сложнее, если у нас есть несколько вариантов второй фазы

• Возможно нарушение принципа единственности ответственности: объект смешивает логику, ради которой он создавался, и сложную процедуру инициализации



В общем случае, желательно, чтобы объект был работоспособен сразу после создания без необходимости вызова дополнительных методов. То есть, чтобы невозможно было создать объект в нерабочем состоянии.



В качестве альтернатив многофазной инициализации всегда стоит рассматривать введение дополнительной функции, классметода или даже применения паттерна абстрактная фабрика.



Дополнительные материалы

http://neo.dmcs.p.lodz.pl/symos/wyklady/04-TwoPhase.pdf

https://wiki.wxpython.org/TwoStageCreation

https://peps.python.org/pep-0489/