Problem
I am currently working with a class that necessarily has a very complicated initialization function (>350 lines of code) given that many computations and attributes need to be performed and defined.
I am considering using either helper functions within the __init__
function of this class in order to make the code cleaner and broken into logical components; however, I am not sure the best way to go about this.
For context, the class is a Neural Network (PyTorch) and the init
function contains several blocks of code that are essentially large if-else trees that define the values of attributes based off of the init
inputs. It is these blocks that I feel are logically distinct and could therefore be placed into their own functions.
Potential Solutions:
In particular, I see 4 potential methods, and I'm hoping that to get clarifications on the pros, cons, and dangers of each of these methods.
I've listed each of the methods out below with some example code - any advice is greatly appreciated!
Method 1
Do not change anything, and keep a very large __init__
function:
class ClassName(SuperClass):
def __init__(self, arg1, arg2, ..., argN):
# <350 lines of code setting attribute values>
Method 2
Define default attribute values at the top of __init__
, and then override them with instance functions based on __init__
's arguments. The reason for listing the attributes out at the top of __init__
is so that all attributes are clearly listed out in one location.
The _fn
s will require access to self
and potentially some __init__
arguments.
class ClassName(SuperClass):
def __init__(self, arg1, arg2, ..., argN):
super().__init__()
# Attribute defaults
self.attr1 = None
self.attr2 = None
# ...
self.attrM = None
# Set attributes
self._fn1()
self._fn2()
# ...
self._fnM()
def _fn1(self, **relevant_args):
# <relevant computations>
self.attr1 = #<result-of-relevant-computations>
def _fn2(self, **relevant_args):
# <relevant computations>
self.attr2 = #<result-of-relevant-computations>
# ...
def _fnM(self, **relevant_args):
# <relevant computations>
self.attrM = #<result-of-relevant-computations>
This has the benefit of compartmentalizing the code
Method 3 (variation of 2)
The same as Method 2 except return a value in the helper functions and then assign those to the instance attributes. Not sure if this makes a difference / is better because it is more explicit about which attributes are being set where. Also has the potential to avoid listing default attribute values which would make the code shorter.
The _fn
s will usually require access to self
and potentially some __init__
arguments.
class ClassName(SuperClass):
def __init__(self, arg1, arg2, ..., argN):
super().__init__()
# Attribute defaults (could potentially remove with this approach)
self.attr1 = None
self.attr2 = None
# ...
self.attrM = None
# Set attributes
self.attr1 = self._fn1()
self.attr2 = self._fn2()
# ...
self.attrM = self._fnM()
def _fn1(self, **relevant_args):
# <relevant computations>
return #<result-of-relevant-computations>
def _fn2(self, **relevant_args):
# <relevant computations>
return #<result-of-relevant-computations>
# ...
def _fnM(self, **relevant_args):
# <relevant computations>
return = #<result-of-relevant-computations>
Method 4
Define inner functions within __init__
that set all relevant attributes, either as in method 2 or method 3. This has the benefit of not cluttering the instance function list, but makes the __init__
function messy.
class ClassName(SuperClass):
def __init__(self, arg1, arg2, ..., argN):
# Helper functions
def fn1(self, **relevant_args):
# <relevant computations>
return #<result-of-relevant-computations>
# ...
def fnM(self, **relevant_args):
# <relevant computations>
return = #<result-of-relevant-computations>
super().__init__()
# Attribute defaults (could potentially remove with this approach)
self.attr1 = None
# ...
self.attrM = None
# Set attributes
self.attr1 = _fn1()
# ...
self.attrM = _fnM()
Finally, I supposed I could define any pure functions at the module level and pass in attributes as arguments, but I do not really think it makes sense to do this since these functions will not be used anywhere else and are pretty specific to the class itself.
Thanks in advance for any advice!
None
), and it would be entirely focused on its specific task.attr_generator
defined at the module level which would take in the arguments that currenty go intoClassName
's__init__
function, perform all necessary computations, and then return an e.g. dictionary of attribute value pairs? And then pass this into the__init__
function to set the instance attributes?