Search code examples
pythonfunctionreturn-value

Keep backward compatibility for a function when we need to return more values than before


I have a function that that currently returns two values, an int and a string, for example:

def myfunc():
    return 0, 'stringA'

This function is already in use in a lot of code, but I'd need to improve it so it returns three values, an int and two strings, for example:

def myfunc():
    return 0, 'stringA', 'stringB'

Of course, I'd like to keep compatibility with existing code, so returning the values like the above modified function will lead to a ValueError.

One solution would be to wrap the improved function into another function with the old name, so we call the initial function in existing code, and the new function in new code, for example:

def newmyfunc():
    return 0, 'A', 'B'

def myfunc():
    result1, result2, _ = newmyfunc()
    return result1, result2

As far as this solution works, I don't really find it elegant.

Is there a better way to achieve this goal? Something like a polymorphic function which could return two or three values without having to modify existing code that uses the function?


Solution

  • First up, answering a question you didn't ask, but which may help in the future or for other folks:

    When I find that I'm returning multiple items from a single function, and especially when the list of items returned starts to grow, I often find it useful to return either a dict or an object rather than a tuple. The reason is that as the returned-item list grows, it becomes harder to keep track of which item's at which index. If the group of returned items are going to be used separately and aren't closely-related other than both coming from the same function, I prefer a dict. If the returned items are being used together in multiple locations (e.g. user name, password, host & port), wrap them all in an object (instantiate a custom class), and just pass that around. YMMV, and it sounds like you're trying to avoid refactoring the code, so:

    The simplest solution to your actual question is to add a keyword argument to your function, set a default on that argument, and use it to decide which version of the arguments to return:

    def myfunc(return_length=2):
        if return_length == 2:
            return 0, 'stringA'
        elif return_length == 3:
            return 0, 'stringA', 'stringB'
        else:
            raise ValueError(f'Unexpected number of return arguments {return_length}')
    

    Old code continues to call the function as-is, and new code explicitly calls my_func(return_length=3). At such point as all the old code gets deprecated, you can change the default value to 3 and/or throw an error when it's set to 2.