Ctrl-C
Ctrl-C

Reputation: 4172

How to group functions without side effects?

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

Answers (1)

John Kugelman
John Kugelman

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

Related Questions