Python – Changing method signature while keeping backwards compatibility

api-designbackward compatibilitypython

I've inherited an API to maintain.

Users can pass a callback function to the class which gets called on some event. The callback function is currently passed in a single argument.

I need to change the expected signature of the callback function to accept additional arguments.

This would be fine if everyones' callback function took in *args and **kwargs, but unfortunately they don't and it's too much work to get everyone to change them.

What is the cleanest way for me to make the change in the API while keeping the callback backwards compatible?

I have this as a shoe-in but it's not very future proof, nor is it very elegant:

...
if self._callback.func_code.co_argcount > 1:
    self._callback( data, additional_arg )
else:
    self._callback( data )
...

Best Answer

In Python, it's "Easier to ask for forgiveness than permission" - it is common "Pythonic" practice to use exceptions and error handling, rather than e.g. if checking up-front ("Look before you leap") to handle potential problems. The documentation provides a few examples that demonstrate where the latter can really cause problems - if the situation changes between the look and the leap, you have serious trouble!

On that basis, and given that a function will raise a TypeError if provided with the wrong number of arguments, you could use:

try:
    # Have a go with the new interface
    self._callback(data, additional_arg)
except TypeError:
    # Fall back to the old one
    self._callback(data)

You could use a decorator function to wrap any callback:

def api_compatible(func):
    @functools.wraps(func)
    def wrapper(data, *args, **kwargs):
        try:
            return func(data, *args, **kwargs)
        except TypeError:
            return func(data)
    return wrapper

Now it becomes:

self._callback = api_compatible(callback)
...
self._callback(data, additional_arg)
Related Topic