Comments 22
Примеры неубедительные, что и понятно, потому что они слишком простые. И если они простые, нет нужды упарываться по паттернам, это частая ошибка.
Ок, допустим, у нас большая сложная инициализация, которую мы хотим упростить. Ну, я бы предпочёл простые поля развернуть как обычно, а объекты передать через Dependency Injection - пока не встречал кода, где этого было бы недостаточно.
Можете пример короткий привести?
Пример чего? Dependency injection?
Вместо
class Backend:
def __init__(self, config):
self.adapter = DbAdapter(user=config["user"], password=config["password"])
def main():
...
backend = Backend(config)
...
Делаем так
class Backend:
def __init__(self, adapter):
self.adapter = adapter
def main():
...
adapter = DbAdapter(config["uri"], config["password"])
backend = Backend(adapter)
...
Я лично даже стараюсь не использовать конструкцию DbAdapter(config["uri"], config["password"])
. Для каждой сущности своя секция в конфиге: DbAdapter(config["database"])
. Или DbAdapter(**config["database"])
для самоуверенных.
В чем разница: передавать config или dbadapter?
Длинно - это соответствует принципам Single Responsibility и Dependency Inversion. Бэкенд не должен заниматься настройкой адаптера к базе и вообще не должен знать о том, что адаптер реально собой представляет.
Коротко - разгружаем init, проще расширяться, проще делать мок-тесты - не возимся с флагами внутри Backend, а просто передаём туда MockAdapter для тестов.
Я его как-то случайно переизобрёл когда из ФП в Питон вернулся. Потом уже вспомнил, что в SOLID'е чёт такое было. В ФП просто это совершенно естественный ход вещей.
__init__
который вернул None
? Что-то подозрительное.
А разве возвращаемое значение этого метода как-то используется? Он же не вызывается явно.
В питоне отсутствие return эквивалентно return None. Если мы аннотируем функции, то возращаемый тип тоже будет type(None) -> None
Все верно - __init__
сам по себе не занимается созданием объекта, это делает __new__
__init__
и должен возвращатьNone иначе будет возбуждено исключение
Python — объектно-ориентированный язык. Способ создания нового объекта обычно определяется в специальном методе
init
, реализованном в классе.
И ведь не смущает автора поста тот факт, что сигнатура метода __ init __ первым аргументом имеет self. Не видно противоречия?
> Если вы не можете выбрать между dataclass
или NamedTuple
, следует отдать предпочтение NamedTuple
, поскольку его поля неизменяемы.
Вы это серьезно? Вы не знаете, что у датаклассов есть frozen? Ваши советы откровенно все какие-то наивные
> @classmethod
def from_file(cls, filepath: str) -> Configuration:
Это не будет работать, проверьте
Это не будет работать, проверьте
Почему?
В таком виде потому что не найдет Configuration как тип возвращаемого значения.
Нужно указывать Self в 3.11+ и имя класса в кавычках как строку если 3.10 и ниже -> "Configuration":
Автор статьи тоже не дурак и в самом начале кода прописал from __future__ import annotations
, что включает Postponed Evaluation of Annotations
А TypedDict вместо DataClass/NamedTuple?
from __future__ import annotations
Статью можно свести к тому, что вместо
class Configuration:
def __init__(self, filepath):
self.filepath = filepath
self._initialize()
следует использовать
class Configuration:
def __init__(self, filepath=None):
self.filepath = filepath
if filepath:
self._initialize_with_filepath()
Но это меняет контракт класса, и теперь возможно сделать конфигурацию без пути.
Контракт класса определяется архитектором, если он считает, что конфигурация без пути не должна существовать, то значит так оно и есть. А вариант "класс может иметь любое внутреннее состояние" (в т.ч. недопустимое с точки зрения бизнес логики) приводит потом к трудноуловимым ошибкам в дебаге.
Например, мы заложились на то, что путь у конфигурации всегда есть, а потом словим NoneType error при попытке по этому пути обратиться.
Или же потом присвоим объекту filepath, забыв сделать инициализацию.
Получится глупо и больно.
Так что не надо такой категоричности.
Тезис "логика создания объектов Python превращается в непонятное чудище" считаю слишком преувеличиваюшим.
init - лишь точка входа при создании объекта. Что скрывает вложенный туда метод или функция - не суть важно, пока все под контролем.
Динамическая типизация, *args *kwargs, инит в который можно засунуть что угодно - это инструменты.
Если конкретно вам что-то не нравится в организации кода в вашем проекте - продвигайте идею интеграции линтеров, удобных вам.
Вероятно, вы неправильно используете метод __init__ в Python