Sometimes I want to write a class with instance variables which should on the one hand be initialized inside __init__
, but on the other hand also later be updatable via different functions (function_1
- function_3
) either from the inside after some event happened or from the outside.
The update functions depend all on the same input parameter, but work the same in initialization and later update. They may be either members of the class (@staticmethod
or not) or utility functions imported from some package.
For the later update, the "meta" update function (update_member_variables
) clearly should be a procedure, i. e. return nothing and only modify the member variables as a side effect.
However, for initialization it should better be a pure function and return the variables' values so that they can be assigned to the variables inside __init__
.
This conflict always puts me through the following cycle of duplicated code, declarations outside __init__
and None
-initializations, but never leads to a satisfying solution:
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)
It is tempting to choose B, but because of all the warnings against member variable declarations outside of __init__
, I usually stick with C or D although they seem bloated and bulky.
Isn't there any better way to solve this situation? Maybe outside the box? Or is the most "elegant" or clean one already among A-D?
Related questions:
In https://stackoverflow.com/a/51607441/3863847 a similar question was answered, but update_number
is only suitable for initialization. The accepted answer's code is like my D, but without update_member_variables
.
In https://stackoverflow.com/a/20661498/3863847 another related question was answered. In general, Simeon Visser states, that it is the developer's responsibility to ensure a consistent object after initialization, but also that it is not strictly necessary clinging to this rule no matter what. Is my case such a case in which it is okay to choose B? The instanciated objects of B would be consistent at least.
In https://stackoverflow.com/a/19292653/3863847 readability is given by sthenault as a reason for why instance variables should not be declared outside __init__
.
As far as I know, this is rooted in PEP 8 and that is why pylint complains about violations - and I never choose B.
sthenault also suggests None-assignments in __init__
, just like progmatico did in the comment below my question, which corresponds to my version C.
Although I was hoping for an elegant trick to somehow circumvent this situation, I am going to consider C as "most pythonic" for the time being. If anyone later comes up with this kind of magical solution I am looking for, I will switch the accepted answer.