Дизайн класса: элегантная инициализация и обновление переменных экземпляра - PullRequest
1 голос
/ 22 апреля 2020

Иногда я хочу написать класс с переменными экземпляра, которые, с одной стороны, должны быть инициализированы внутри __init__, но, с другой стороны, позже могут быть обновлены с помощью других функций (function_1 - function_3) либо из внутри после какого-либо события или снаружи.

Все функции обновления зависят от одного и того же входного параметра, но работают одинаково при инициализации и последующем обновлении. Они могут быть либо членами класса (@staticmethod или нет), либо служебными функциями, импортированными из некоторого пакета.

Для более позднего обновления, функция обновления «meta» (update_member_variables) явно должна быть процедурой т.е. ничего не возвращать и только изменять переменные-члены как побочный эффект. Однако для инициализации лучше использовать чистую функцию и возвращать значения переменных, чтобы их можно было присвоить переменным внутри __init__.

Этот конфликт всегда приводит меня к следующему циклу дублированного кода: объявления вне __init__ и None -инициализации, но никогда не приводят к удовлетворительному решению:

from some_utils import function_1, function_2, function_3


# A: duplicate code in update_member_variables
class Class:
    def __init__(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)

    def update_member_variables(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)


# B: instance variables declared outside __init__
class Class:
    def __init__(self, parameter):
        self.update_member_variables(parameter)

    def update_member_variables(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)


# C: boilerplate None-assignments
class Class:
    def __init__(self, parameter):
        self._variable_1 = None
        self._variable_2 = None
        self._variable_3 = None
        self.update_member_variables(parameter)

    def update_member_variables(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)


# D: back to duplicated code in update_member_variables
class Class:
    def __init__(self, parameter):
        (
            self._variable_1,
            self._variable_2,
            self._variable_3
        ) = self._derive_values(parameter)

    def _derive_values(self, parameter):
        return (
            function_1(parameter),
            function_2(parameter),
            function_3(parameter),
        )

    def update_member_variables(self, parameter):
        (
            self._variable_1,
            self._variable_2,
            self._variable_3
        ) = self._derive_values(parameter)

Заманчиво выбрать B, но из-за всех предупреждений против объявлений переменных-членов вне __init__, Я обычно придерживаюсь C или D, хотя они кажутся раздутыми и громоздкими.

Нет ли лучшего способа решить эту ситуацию? Может быть, нестандартно? Или самый «элегантный» или чистый из уже существующих в нашей эры?

Похожие вопросы:

В { ссылка } на аналогичный вопрос был дан ответ, но update_number есть подходит только для инициализации. Код принятого ответа похож на мой D, но без update_member_variables.

В { ссылка } был дан ответ на другой связанный вопрос. В целом, Симеон Виссер заявляет, что ответственность за обеспечение целостности объекта после инициализации лежит на разработчике, но также не обязательно придерживаться этого правила, несмотря ни на что. Является ли мой случай таким, в котором можно выбрать B? Инстанцированные объекты B будут согласованы по крайней мере.

Ответы [ 2 ]

1 голос
/ 22 апреля 2020
class A:
    def __init__(self, parameter):
        self._variable_1 = function_1(parameter)
        self._variable_2 = function_2(parameter)
        self._variable_3 = function_3(parameter)

    @property
    def variable_1(self):
        return self._variable_1

    @variable_1.setter
    def variable_1(self, value):
        self._variable_1 = function_1(value)

    ... so on and so forth for other variables ...

 a = A(parameter1)
 # update based on parameters
 a.variable_1 = parameter2

Я чувствую, что используя свойство, вы можете обновить свои переменные лучше.

0 голосов
/ 27 апреля 2020

В { ссылка } удобочитаемость задается sthenault в качестве причины, по которой переменные экземпляра не должны объявляться вне __init__.

Насколько я знаю, это связано с PEP 8, и именно поэтому Pylint жалуется на нарушения - и я никогда не выбираю B.

sthenault также предлагает отсутствие назначений в __init__, как это сделал progmatico в комментарии под моим вопросом, что соответствует моей версии C.

Хотя я надеялся, что изящная уловка каким-то образом обойдет эту ситуацию, я собираюсь считать C «большинством питонов» 1015 * »на данный момент. Если кто-нибудь позже придет к такому волшебному решению, которое я ищу, я переключу принятый ответ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...