{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Optional notebook - Lambda functions, functions as variables, and decorators\n", "\n", "We saw list comprehensions as syntactic sugar to replace for loops. In this lecture, we'll look at lambda functions, which are to normal functions as list comprehensions are to for loops, and the concept of functions as variables.\n", "\n", "### Lambda functions\n", "\n", "We'll look at an example of lambda functions below:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# The usual way that we're used to\n", "def f(x):\n", " return x**2\n", "\n", "# Lambda functions\n", "g = lambda x: x**2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These two functions will do the same thing for any action. Although we haven't saved much space by using lambda functions here, they can be used to great effect in some cases to create beautiful, succint code. Their syntax is as follows:\n", "\n", " function_name = lambda arguments: what to return\n", " \n", "The way their syntax is defined makes them similar to mathematical functions that we're used to. \n", "\n", "### Functions as variables\n", "\n", "As you may have noticed, the syntax used for making lambda functions is pretty similar to the syntax we use for assigning objects to variables. We can also pass around functions like we do variables too. Look at the following scenario:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "140.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def sum_f(x, function):\n", " tot = 0.\n", " for _x in x:\n", " tot += function(_x)\n", " return tot\n", "\n", "x = [0., 1., 2., 3., 4., 5., 6., 7.]\n", "sum_f(x, g)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can use sum_f to compute a sum of squares, or a sum of square roots, or anything, as long as we pass to it a function which takes as its argument some float and returns a float. In the example above, we used sum_f(x, g), but sum_f(x, f) also works; we can pass not only lambda functions but normal functions too to a function.\n", "\n", "Because you can use functions as variables, you could even make a list of functions, which could be used to represent a series! The script [Fourier Series.ipynb](https://github.com/PyCav/Demos/blob/master/Maths/Fourier%20Series.ipynb) shows this in action. There are some caveats to doing this though, which are covered in Common Mistakes in Python.\n", "\n", "We can also of course return functions, in the same way we return variables:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-9.79830126608193\n" ] } ], "source": [ "def gravity_from_body(with_mass):\n", " G = 6.674 * (10**(-11))\n", " def g(r):\n", " return - (G * with_mass) / (r**2)\n", " return g\n", "\n", "earth_radius = 6378 * (10**3)\n", "earth_mass = 5.97219*(10**24)\n", "earth_gravity = gravity_from_body(with_mass=earth_mass)\n", "print(earth_gravity(earth_radius))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This allows us to write one generic function which can return the appropriate functions for different situations. This brings us on to the final topic for this notebook, function decorators.\n", "\n", "### Function decorators\n", "\n", "Imagine a function which takes a function and returns a new function that does something different, as shown below:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "25.0\n" ] } ], "source": [ "def print_output_from(function):\n", " def new_func(*args, **kwargs):\n", " print(str(function(*args, **kwargs)))\n", " return new_func\n", "\n", "h = print_output_from(f)\n", "h(5.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is all that a function decorator is. It takes a function, and \"decorates\" it, so that it now has some added capabilities that the base function didn't have. It's almost like subclassing! This can be very useful, but the syntax is a bit cumbersome. Instead, we can use python's decorator syntax, as shown below:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "125.0\n" ] } ], "source": [ "@print_output_from\n", "def f(x):\n", " return x**3\n", "\n", "f(5.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This syntax s very clean, and we can also have multiple decorators for any function. There are a number of function decorators that are very useful. To name a few:\n", "\n", "* numpy.vectorize: Allows your function to act on numpy arrays, in the same way numpy.sin and numpy.cos do, on top of being able to act on single elements of arrays.\n", "\n", "* The property decorator: Allows you to call a method in a class the same way you would call a variable. This allows for you to get computed properties in a very nice way. E.g. particle.kinetic_energy makes more sense than particle.kinetic_energy() as it is a property of the system, but it's a property that needs to be dynamically computed. Another use case is if other variables should be set when one variable is changed. Its usage is a little involved:\n", " @property\n", " def property_name(self):\n", " some actions\n", " A popular convention if you're only making these properties for special behaviour when you're setting the variable is to just have return self._property_name here. The underscore is required as else the function will just keep calling itself, going into a loop.\n", " \n", " @property_name.setter\n", " def property_name(self, new_value):\n", " some actions\n", " Another thing to note if you're using the property decorator is not to call it in the \\_\\_init\\_\\_ block of code as it's only initialised after the object is initialised.\n", " \n", "* And more..." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.1" } }, "nbformat": 4, "nbformat_minor": 0 }