0

I've defined a bunch of custom functions and find a lot of them include some identical or similar blocks of code (e.g. just including slightly different strings or arguments). So something like:

def func1(a, b, c):
    some_identical_code
    some_similar_code
    more_identical_code
    some_unique_code
    final_similar_code

# similarly for func2, func3...

I would like to try to combine these into a single function that takes an additional 'setting' argument, both because the functions are obviously related and to make the code more compact. The obvious but inelegant flow for this would be something like:

def func(a, b, c, setting):
    # setting = '1', '2', etc.

    some identical code

    if setting == '1':
        similar_code_1
    elif setting == '2':
        similar_code_2
    # etc. for other settings options

    more_identical_code

    if setting == '1':
        unique_code_1
        final_similar_code_1
    elif setting == '2':
        unique_code_2
        final_similar_code_2
    # etc. for other settings options

How can I do something like this more elegantly? Is there a standard way to do it?

One option I'm trying is to use dictionaries built into the function to parse the similar code blocks:

def func(a, b, c, setting):
    # setting = '1', '2', etc.
    simdict = {'1': '_1', '2': '_2' ...}

    some identical code
    similar_code(simdict[setting])
    more_identical_code

    if setting == '1':
        unique_code_1
    elif setting == '2':
        unique_code_2
    # etc. for other settings options

    final_similar_code(simdict[setting])

This helps but 1) I'm not sure if it's good design and 2) it only helps so much when the identical vs. similar vs. unique code is very interspersed.

4
  • You could use some decorators. They basically wrap one function in another. This will give you separate functions, but with shared code. Commented Jun 2, 2020 at 13:42
  • 3
    I think the usual way is to refactor some_identical_code, more_identical_code and final_similar_code into separate functions (maybe with parameters as in the third case) and call them from func1, func2 etc. Commented Jun 2, 2020 at 13:45
  • Could you give more details about the actual code? Stef has a good suggestion, but depending on what the actual identical code does, it might not be feasible. Commented Jun 2, 2020 at 13:56
  • For the functions that inspired this question, the identical and similar code blocks basically modify / compile a set of instructions, which is then fed to another custom function which calls a specific modelling software using the aforementioned set of instructions. The unique code blocks are generally workflow control. Commented Jun 2, 2020 at 14:12

3 Answers 3

1

At the risk of over-engineering your problem, you may use an abstract class and force child to specialize the parts of your procedure. This is possible using the abc library in Python.

from abc import ABC, abstractmethod


class AbstractProcessor(ABC):

    def process(self):
        some_identical_code
        self.some_similar_code()
        more_identical_code

    @abstractmethod
    def some_similar_code(self):
        pass

class Processor1(AbstractProcessor):

    def some_similar_code(self):
        print("Proc 1")

class Processor2(AbstractProcessor):

    def some_similar_code(self):
        print("Proc 2")


proc1 = Processor1()
proc1.process()
Sign up to request clarification or add additional context in comments.

2 Comments

I am a fan of occasional over-engineering for future utility :) How is this different from the answer from @One Lyner - what does using the abc library add? I am not familiar with it at all...
That's actually the same principle that the one used by One Lyner. The abc library makes the intent clearer that one should not instantiate AbstractProcessor but only the child. Also, using @abstractmethod, if an implementation is missing the exception will be raised sooner (when calling Processor1() instead of process()) . But in the end, using abc is totally optional.
1

Abstract!

  • identical code should be a function;
  • similar code should be a function with a parameter;
  • unique code should be one function for each;
  • object oriented code can help here.

To get the idea:

def identical_code():
    lorem ipsum

def similar_code(parameter):
    lorem ipsum

class Base:
    def __call__(self, a, b, c):
        identical_code()
        similar_code(self.parameter())
        unique_code()
    def unique_code(self):
        NotImplemented
    def parameter(self):
        NotImplemented

class Func_1(Base):
    def unique_code(self):
        do_it_1()
    def parameter(self):
        return 1

class Func_2(Base):
    def unique_code(self):
        do_it_2()
    def parameter(self):
        return 2

You can then call Func1()(a, b, c) or Func_2()(a, b, c).

Here the NotImplemented methods are there to show that the base class is just there to define the common behavior, but can't really do anything if you don't specify those two methods.

Some maybe more pythonic way would be to just duck type and remove inheritance:

def func(a, b, c, setting):
    identical_code()
    similar_code(setting.parameter())
    setting.unique_code()

class Func_1:
    def unique_code(self):
        do_it_1()
    def parameter(self):
        return 1

class Func_2:
    def unique_code(self):
        do_it_2()
    def parameter(self):
        return 2

def func1(a, b, c):
    return func(a, b, c, Func_1)

def func2(a, b, c):
    return func(a, b, c, Func_2)

2 Comments

Interesting; could you elaborate on 1) how you would then use / call the function variants, and 2) why you have the 'Not implemented' blocks in the base class?
@TYLim I added some more comments, and another design option.
0

Use a function to generate functions from the shared code. Say part of your shared code adds a constant to a value that's passed in:

def adder(n):
  def addTo(x):
    return x + n
  return addTo

Then, you need an adder that adds 2 and one that adds 4:

add2 = adder(2)
print(add2(2)) # Prints 4

add4 = adder(4)
print(add4(2)) # Prints 6

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.