1

I have different equations to compute the same quantity. For example, val = my_func(a, b, c) but also val = my_func(x, y), where my_func represents the quantity I would like to compute: it could be Velocity or Momentum or something completely different. I have dozens of quantities to code, each with multiple equations.

It is also essential that I provide a decent documentation for the different input arguments.

EDIT: I believe the end users of the code will be smart enough to use some kind of advanced editor with autocompletion and capable of displaying docstrings.

So far, I have thought about three basic approaches.

Approach #1: I can write one function for each equation.
Pros: easy to write.
Cons: From a developer standpoint it may be difficult to maintain the code as it grows. From a user standpoint it becomes "cumbersome", in the sense that the user is forced to look at the correct function that accepts its arguments.

def my_func_1(a, b, c):
    """
    Compute my_func.

    Parameters
    ----------
        a : float
            blablabla
        b : float
            blablabla
        c : float
            blablabla
    """
    return a + b + c

def my_func_2(x, y):
    """
    Compute my_func.

    Parameters
    ----------
        x : float
            blablabla
        y : float
            blablabla
    """         
    return x * y

Approach #2: I can write one function accepting keywords arguments and manage all the different cases!
Pros: Easy to use from a user standpoint (the user only need to read the documentation of one function and insert the correct parameters).
Cons: From a developer standpoint (in my opinion) it requires a little bit more attention to what you are coding. From a user standpoint, it becomes a little bit harder to use as he/she is forced to use the keyword approach, for example: my_func(x=my_x_val, y=my_y_val).

def my_func(**args):
    """
    Compute my_func. Formulas:
        (1) val = my_func(a, b, c)
        (2) val = my_func(x, y)

    Please, provide input parameters accordingly to the equation 
    you desire to solve.

    Parameters
    ----------
        a : float
            blablabla
        b : float
            blablabla
        c : float
            blablabla
        x : float
            blablabla
        y : float
            blablabla
    """

    if all(k in args for k in ["a", "b", "c"]):
        a, b, c = args["a"], args["b"], args["c"]
        return a + b + c
    if all(k in args for k in ["x", "y"]):
        x, y = args["x"], args["y"]
        return x * y

    raise ValueError("The given arguments can't be used to compute the " + 
        "my_func. Please, read the function docs.")

Approach #3: I can write one function accepting a index argument associated to the equation the user would like to solve. This function return the correct equation (function) and the user insert the different arguments.
Pros: Easy to use from a user standpoint as there is no need for keywords arguments! For example, my_val = my_func(2)(my_x_val, my_y_val).
Cons: From a user standpoint it would be a good idea to also have a copy of the arguments description in the parent function docstring, but this would insert a lot of repetition in the comments, hence increase difficulty in maintaining the code.

def my_func(idx):
    """
    Compute my_func. Formulas:
        (1) val = my_func(1)(a, b, c)
        (2) val = my_func(2)(x, y)

    Please, insert the index of the formulas you would like to solve.
    """

    if idx == 1:
        def func(a, b, c):
            """
            Parameters
            ----------
                a : float
                    blablabla
                b : float
                    blablabla
                c : float
                    blablabla
            """
            return a + b + c
        return func
    if idx == 2:
        def func(x, y):
            """
            Parameters
            ----------
                x : float
                    blablabla
                y : float
                    blablabla
            """
            return x * y
        return func

    raise ValueError("The given index can't be used to compute the " + 
        "my_func. Please, read the function docs.")

As of now, I would be inclined to use approach #2.

Is there any better paradigm for this problem? Do you happen to know some code that faced this problem?

  • 3
    all of your proposed alternatives are massively more complex than the original separate functions. In your my_fun(idx)(a,b) solution you even have all of the original functions, only inserted in some dispatch mechanism. – amon Jul 2 at 12:17
  • @amon , (part 1) the dispatch mechanism allows the end user to quickly chose which equation to compute without having to go through all the different functions of the same quantity (ie, reading the docs of all the functions)! I believe the end user would be smart enough to be coding with some kind of advanced editor with autocompletion and capable of displaying docstrings. Now, consider this example, you want to compute my_func with arguments a, b, c. – Davide_sd Jul 2 at 12:47
  • @amon (part 2) With approach #1, if I (developer) have coded dozens of my_func_{number} you have to search for the correct function (probably) every time. What if you use my package every so often? You forget which function is the correct one for your occasion, forcing you to go through the docs again, and by the end of the day, time is money, you have wasted a good amount of time going trough docs. This is only for one quantity, now think of dozens of them... I'm looking for a good compromise between development effort and usability. – Davide_sd Jul 2 at 12:47
  • 1
    @Davide_sd: You can't entirely avoid that (with #3, the devs will have to look at the docs anyway, and with #2, sometimes -e.g., func takes 3 numbers, and there are 2 versions that do). The real question is, is your assumption about this being a significant bottleneck correct? They may look at the docs once or twice, until they find what they need and then just use that rest of the time; the bottleneck will likely be elsewhere, in their own domain. Also, looking at docs is part of the deal (I can't read your mind). #1 is super easy for maintainers, and a good IDE will make it easy for users. – Filip Milovanović Jul 2 at 13:46
  • 2
    P.S. A good naming convention and meaningful names (for intended audience) will also help - e.g. for related functions, start all with the same word, then add a suffix that describes the variant (not always feasible, but do it when possible). E.g. variance_sample and variance_population to let your users know if the function uses the Bessel correction (n-1 in the denominator). – Filip Milovanović Jul 2 at 14:00
2

If I were doing this, I would go with Approach 1. Without more details, I can't say for sure but if you have good unit tests, maintainability shouldn't be much of a problem. If there is a lot of common code among the functions, then obviously put that common code in its own function/module so you're not touching a dozen methods to change one thing.

Approach 1 is also much clearer and easier for the user.

Regarding approach 2... From the user point of view, I personally dislike methods with required keyword arguments. It makes for extra typing and you always have to look up what the keywords are.

Approach 3 is definitely the worst unless you used keywords instead of indices but then it's still a strange approach and now you have the same problem as #2.

Both approaches 2 & 3 make testing more difficult in that it is harder to know that you've tested everything and tests are more complicated.

Also, in terms of maintainability, having one method do a lot of things means that when you have to change it, you increase the chances of breaking something else (e.g., insert an innocuous early return statement that skips vital code.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.