{ "metadata": { "name": "", "signature": "sha256:a93737702b08a2fa642c7d8d48d2c21bc861517829a003be0e34b3088ed649f6" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Version 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "My software has a callback API, to call functions with one argument:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "tone_detected_callbacks = []\n", "def on_tone_detected(callback):\n", " tone_detected_callbacks.append(callback)\n", "\n", "def tone_detected(pitch):\n", " for callback in tone_detected_callbacks:\n", " callback(pitch)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sara writes a plugin which provides a callback:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def tone_callback_a(pitch):\n", " print(\"Tone detected at %f Hz\" % pitch)\n", "\n", "on_tone_detected(tone_callback_a)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "And there was much rejoicing." ] }, { "cell_type": "code", "collapsed": false, "input": [ "tone_detected(227.5)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Tone detected at 227.500000 Hz\n" ] } ], "prompt_number": 3 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Version 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The software becomes more complex, and it can provide more information to callbacks:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "def tone_detected(pitch, duration):\n", " for callback in tone_detected_callbacks:\n", " callback(pitch, duration)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "But Sara's plugin hasn't been updated yet, so it doesn't expect the extra parameter." ] }, { "cell_type": "code", "collapsed": false, "input": [ "tone_detected(227.5, 3)" ], "language": "python", "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "tone_callback_a() takes 1 positional argument but 2 were given", "output_type": "pyerr", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mtone_detected\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m227.5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36mtone_detected\u001b[1;34m(pitch, duration)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mtone_detected\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpitch\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mduration\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mcallback\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mtone_detected_callbacks\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[0mcallback\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpitch\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mduration\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mTypeError\u001b[0m: tone_callback_a() takes 1 positional argument but 2 were given" ] } ], "prompt_number": 5 }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Using backcall" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`backcall` is a library to solve this problem, so you can extend callback APIs in a backwards compatible way." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from backcall import callback_prototype\n", "\n", "# A callback prototype specifies what parameters we're going to pass\n", "\n", "@callback_prototype\n", "def tone_detected_cb(pitch, duration):\n", " pass\n", "\n", "tone_detected_callbacks = []\n", "def on_tone_detected(callback):\n", " # This inspects callback, and wraps it in a function that will discard extra arguments\n", " adapted = tone_detected_cb.adapt(callback)\n", " tone_detected_callbacks.append(adapted)\n", "\n", "def tone_detected(pitch, duration):\n", " for callback in tone_detected_callbacks:\n", " callback(pitch, duration)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Registering the callback looks just the same as before - callback providers don't need to do anything special." ] }, { "cell_type": "code", "collapsed": false, "input": [ "on_tone_detected(tone_callback_a)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now the extra parameter is discarded, and Sara's plugin gets only the information it expects." ] }, { "cell_type": "code", "collapsed": false, "input": [ "tone_detected(227.5, 3)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Tone detected at 227.500000 Hz\n" ] } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plus you've got an introspectable reference for the expected callback signature:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "help(tone_detected_cb)" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Help on function tone_detected_cb in module __main__:\n", "\n", "tone_detected_cb(pitch, duration)\n", "\n" ] } ], "prompt_number": 9 } ], "metadata": {} } ] }