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

Time:05-26

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?

CodePudding user response:

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.

CodePudding user response:

An example with decorators: the body of the involved functions stays untouched, the "modification"-part is delegated to an external function, the decorator.

Assumed "ground functions" take no arguments.

def dec(f_reference):
    return lambda f_extra_feature: lambda:(*f_reference(), f_extra_feature())

def myfunc():
    return 0, 'stringA'

def xxxfunc():
    return 'XXX'

myfunc = dec(f_reference=myfunc)(f_extra_feature=xxxfunc)

print(myfunc)
#(0, 'stringA', 'XXX')

Depending on the needs the second parameter, f_extra_feature, can be made implicit.

A less flexible decoration could be done with the syntactic sugar notation

# order of argument is changed!
def dec2(f_extra_feature):
    return lambda f_reference: lambda:(*f_reference(), f_extra_feature())

def xxxfunc():
    return 'XXX'

@dec2(f_extra_feature=xxxfunc)
def myfunc():
    return 0, 'stringA'

print(myfunc())
#(0, 'stringA', 'XXX')
  • Related