My software has a callback API, to call functions with one argument:
tone_detected_callbacks = []
def on_tone_detected(callback):
tone_detected_callbacks.append(callback)
def tone_detected(pitch):
for callback in tone_detected_callbacks:
callback(pitch)
Sara writes a plugin which provides a callback:
def tone_callback_a(pitch):
print("Tone detected at %f Hz" % pitch)
on_tone_detected(tone_callback_a)
And there was much rejoicing.
tone_detected(227.5)
Tone detected at 227.500000 Hz
The software becomes more complex, and it can provide more information to callbacks:
def tone_detected(pitch, duration):
for callback in tone_detected_callbacks:
callback(pitch, duration)
But Sara's plugin hasn't been updated yet, so it doesn't expect the extra parameter.
tone_detected(227.5, 3)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-5-fc61d95b6669> in <module>() ----> 1 tone_detected(227.5, 3) <ipython-input-4-dc14bf1e421f> in tone_detected(pitch, duration) 1 def tone_detected(pitch, duration): 2 for callback in tone_detected_callbacks: ----> 3 callback(pitch, duration) TypeError: tone_callback_a() takes 1 positional argument but 2 were given
backcall
is a library to solve this problem, so you can extend callback APIs in a backwards compatible way.
from backcall import callback_prototype
# A callback prototype specifies what parameters we're going to pass
@callback_prototype
def tone_detected_cb(pitch, duration):
pass
tone_detected_callbacks = []
def on_tone_detected(callback):
# This inspects callback, and wraps it in a function that will discard extra arguments
adapted = tone_detected_cb.adapt(callback)
tone_detected_callbacks.append(adapted)
def tone_detected(pitch, duration):
for callback in tone_detected_callbacks:
callback(pitch, duration)
Registering the callback looks just the same as before - callback providers don't need to do anything special.
on_tone_detected(tone_callback_a)
Now the extra parameter is discarded, and Sara's plugin gets only the information it expects.
tone_detected(227.5, 3)
Tone detected at 227.500000 Hz
Plus you've got an introspectable reference for the expected callback signature:
help(tone_detected_cb)
Help on function tone_detected_cb in module __main__: tone_detected_cb(pitch, duration)