Reputation: 4172
I have a function with several helper functions. That's fairly common case. I want to group them in a common context for readability and I'm wondering how to do it right.
Simplified example:
def create_filled_template_in_temp(path, values_mapping):
template_text = path.read_text()
filled_template = _fill_template(template_text, values_mapping)
result_path = _save_in_temp(filled_template)
return result_path
def _fill_template(template_text, values_mapping):
...
def _save_in_temp(filled_template):
_, pathname = tempfile.mkstemp(suffix='.ini', text=True)
path = pathlib.Path(pathname)
path.write_text(text)
return path
...
create_filled_template_in_temp(path, values_mapping)
Please note that I don't want the helper methods on the module level because they belong to only one method. Imagine having several such examples as above in the same module. Maany non-public functions on module level. A mess (and this happens many times). Also I'd like to give them context and use the context's name to simplify the naming inside.
Solution #0: A module
Just put it in another module:
template_fillers.create_in_temp(path, values_mapping)
Problems:
Finally this is just too little code to add a module for it.
Solution #1: A class
Create a class with no __init__
and only one public (by naming convention) method:
class TemplateFillerIntoTemp:
def run(self, path, values_mapping):
template_text = path.read_text()
filled_template = self._fill_template(template_text, values_mapping)
result_path = self._save_in_temp(filled_template)
return result_path
def _fill_template(self, template_text, values_mapping):
...
def _save_in_temp(self, filled_template):
_, pathname = tempfile.mkstemp(suffix='.ini', text=True)
path = pathlib.Path(pathname)
path.write_text(text)
return path
...
TemplateFillerIntoTemp().run(path, values_mapping)
This is what I did many times in the past. Problems:
Solution #2: Static class
Take solution #1, add @staticmethod
everywhere. Possibly also ABC metaclass.
TemplateFillerIntoTemp.run(path, values_mapping)
Pro: there is a clear indication that this all is instance-independent. Con: there's more code.
Solution #3: Class with a __call__
Take solution #1, create a __call__
function with the main method, then create on module level a single instance called create_filled_template_in_temp
.
create_filled_template_in_temp(path, values_mapping)
Pro: calls like a single function. Con: implementation is overblown, not really fit for the purpose.
Solution #4: Insert helper functions into main function
Add them inside.
def create_filled_template_in_temp(path, values_mapping):
def _fill_template(template_text, values_mapping):
...
def _save_in_temp(filled_template):
_, pathname = tempfile.mkstemp(suffix='.ini', text=True)
path = pathlib.Path(pathname)
path.write_text(text)
return path
template_text = path.read_text()
filled_template = _fill_template(template_text, values_mapping)
result_path = _save_in_temp(filled_template)
return result_path
...
create_filled_template_in_temp(path, values_mapping)
Pro: this looks well if total number of lines is small and there are very few helper functions. Con: it doesn't otherwise.
Upvotes: 11
Views: 268
Reputation: 361730
Modification of #4: Make inner functions, and also have the function's body be an inner function. This has the nice property of still reading top-to-bottom, rather than having the body all the way at the bottom.
def create_filled_template_in_temp(path, values_mapping):
def body():
template_text = path.read_text()
filled_template = fill_template(template_text, values_mapping)
result_path = save_in_temp(filled_template)
return result_path
def fill_template(template_text, values_mapping):
...
def save_in_temp(filled_template):
_, pathname = tempfile.mkstemp(suffix='.ini', text=True)
path = pathlib.Path(pathname)
path.write_text(text)
return path
return body()
(I don't care for the leading underscores, so they didn't survive.)
Upvotes: 11