{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "![header](header.png)\n", "# Defining functions\n", "\n", "We have seen that there are many useful functions built-in to Python, and many more available sitting in the standard library and other modules just waiting to be imported to help us complete tasks. This is far from the end of the story though. We previous defined a function to be a command that takes an input argument, and returns an output. The real power of functions, however, is that we can create our own, making a piece of code that can be reused anywhere in our program, or imported into other programs.\n", "\n", "Remember, the key to programming is breaking down large complex tasks into small simple tasks. Functions are the key to doing this. They help us to organize and formalize this process by turning each small task into a separate function. These functions can then be reused and combined over and over. For this reason, you should virtually never have to copy and paste code to complete several similar tasks: the correct thing to do is create a function that completes these tasks, and use it several times.\n", "\n", "This leads to the concept computer scientists refer to as \"abstraction\". It means that a problem can be solved once, and thereafter treated as single step in an algorithm, however complex that problem may have been initially. For example, we have used the range() function a lot to get a sequence of numbers. Do we know how it works? No. Do we need to? No! It was solved earlier by someone else. Now we will learn how to make our own functions, so that that someone else can be you.\n", "\n", "Once again, functions in the code are indicated by a header, followed by the correct indentation. The keyword for the header is def, for \"define\". The function then executes until it reaches the end of the indented text or, more importantly, until it reaches the return keyword, which tells the function what to output." ] }, { "cell_type": "code", "execution_count": 38, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def a_silly_function():\n", " # We are now defining a function\n", " print(\"This function does nothing\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Defining a function does nothing right away! We have to call it to make the magic happen." ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This function does nothing\n" ] } ], "source": [ "a_silly_function() # a function is executed by typing its name followed by brackets." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The brackets to call the function are very important. If we refer to a function without the brackets, we are just referring to it, not \"activating\" it. For instance, we can set a function to a variable:" ] }, { "cell_type": "code", "execution_count": 52, "metadata": { "collapsed": true }, "outputs": [], "source": [ "silly = a_silly_function" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "This function does nothing\n" ] } ], "source": [ "silly()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Parameters and arguments\n", "\n", "As mentioned, functions typically take one or more inputs, making them capable of being used in more situations. Inside the function definition, these inputs are given placeholder names, called parameters, which behave like temporary variables. The parameters are specified in the brackets in the header of the function:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def says_a_phrase(phrase):\n", " print('\"' + phrase + ',\" said the computer')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The inputs given to each use of the function are called arguments. Each time the function is executed, the parameters in the function definition are substituted for the arguments provided when the function is called:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"Hello,\" said the computer\n", "\"I think, therefore I am,\" said the computer\n", "\"But I cannot think, so perhaps I am not,\" said the computer\n" ] } ], "source": [ "says_a_phrase(\"Hello\")\n", "says_a_phrase(\"I think, therefore I am\")\n", "says_a_phrase(\"But I cannot think, so perhaps I am not\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, phrase is the parameter, \"Hello\" and so on are the arguments.\n", "\n", "We can provide functions with multiple arguments. There are two ways of doing this: by position, and by keyword. Firstly, by position:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def says_a_phrase_repeatedly(phrase, repeat):\n", " for x in range(repeat):\n", " says_a_phrase(phrase) # notice we are calling the previously defined function inside this function!" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"Error,\" said the computer\n", "\"Error,\" said the computer\n", "\"Error,\" said the computer\n", "\"Error,\" said the computer\n", "\"Error,\" said the computer\n" ] } ], "source": [ "says_a_phrase_repeatedly(\"Error\", 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, the order of the arguments provided must match the order of the parameters in the definition if the function is to work correctly. The following will not work:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'str' object cannot be interpreted as an integer", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\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[0msays_a_phrase_repeatedly\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"Error\"\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;36msays_a_phrase_repeatedly\u001b[1;34m(phrase, repeat)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0msays_a_phrase_repeatedly\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mphrase\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mrepeat\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrepeat\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[0msays_a_phrase\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mphrase\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# notice we are calling the previously defined function inside this function!\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mTypeError\u001b[0m: 'str' object cannot be interpreted as an integer" ] } ], "source": [ "says_a_phrase_repeatedly(5, \"Error\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Positional arguments are useful when there is only a small number of arguments, and the order is easy to remember.\n", "\n", "The alternative is keyword arguments. We can use the keyword arguments like so:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"0110111,\" said the computer\n", "\"0110111,\" said the computer\n", "\"0110111,\" said the computer\n", "\"0110111,\" said the computer\n", "\"0110111,\" said the computer\n" ] } ], "source": [ "says_a_phrase_repeatedly(phrase=\"0110111\",repeat=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now the order doesn't matter:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"0110111,\" said the computer\n", "\"0110111,\" said the computer\n", "\"0110111,\" said the computer\n" ] } ], "source": [ "says_a_phrase_repeatedly(repeat=3,phrase=\"0110111\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is very useful when there are several arguments to pass to a function, and it is not clear in what order these should come. It can also make the meaning of the code clearer when the function is being called. The two approaches of positional and keyword arguments can be combined, so long as we follow the rule that positional arguments come first, followed by keyword arguments." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another useful aspect of keyword arguments is that they can have default values set in the function definition. If the keyword argument is omitted from the function call, the default value is used. This cannot be done with positional arguments, because omitting an argument in this case would change the position of all arguments that follow!" ] }, { "cell_type": "code", "execution_count": 34, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def says_a_phrase_backwards(phrase, repeat=5):\n", " for i in range(repeat):\n", " says_a_phrase(phrase[::-1])" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\"rorre suoireS,\" said the computer\n", "\"rorre suoireS,\" said the computer\n", "\"rorre suoireS,\" said the computer\n", "\"rorre suoireS,\" said the computer\n", "\"rorre suoireS,\" said the computer\n", "\"toobeR,\" said the computer\n", "\"toobeR,\" said the computer\n", "\"toobeR,\" said the computer\n" ] } ], "source": [ "says_a_phrase_backwards(\"Serious error\") # uses default value for repeat\n", "says_a_phrase_backwards(\"Reboot\", repeat=3) # uses the provided value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Scope\n", "\n", "A key advantage (and source of confusion for beginners) when using functions is the idea of scope of variables.\n", "\n", "We are quite used to assigning variables in the main body of our code. These are called global variables. A variable assigned inside of a function block is called a local variable. It has no meaning beyond the boundaries of the function. Once the function has completed its task, the variable is promptly forgotten about.\n", "\n", "While this does lead to mistakes and frustration early on, the benefits are roughly twofold. For starters, this means that the same variable name can be reused in different contexts -- we don't always have to come up with something new. Secondly, this makes it much less likely that functions will interfere with one another's inner workings. If functions could easily modify global variables, or the variables local to other functions, we would soon run in to errors. However, if each function is allowed to operate as a self-contained unit, this cannot happen. Example:" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'b' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[0massigns_some_variables\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mb\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'b' is not defined" ] } ], "source": [ "def assigns_some_variables():\n", " a = 1\n", " b = 2\n", " c = 3\n", "\n", "assigns_some_variables()\n", "print(b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If, for some reason, we do want a function to set a global variable, we can use the keyword global, like so" ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2\n" ] } ], "source": [ "def assigns_some_variables():\n", " global a, b\n", " a = 1\n", " b = 2\n", " c = 3\n", "\n", "assigns_some_variables()\n", "print(b)" ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'c' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\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[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mc\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'c' is not defined" ] } ], "source": [ "print(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you have to do this too often though, it's likely a sign that your design is flawed in some way. In most larger programs, only a few variables are truly necessary to be global; most variables we set are just stepping stones toward the main results, and should be kept local. You may often here the mantra \"no global variables\". For small scripts performing a single simple task, this is probably overkill, but it's worth bearing in mind for larger projects where conflict and interference between variables becomes more likely." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Returning an output\n", "\n", "So far, these functions haven't really given us an output. Rather, they've just been calling the print function, which displays some text to the screen but doesn't actually evaluate to anything, in the sense that a mathematical expression evaluates to a number, or a logical expression evaluates to a boolean.\n", "\n", "A return statement is a line in a function that tells the function to stop executing, and output a given value. One function can have several return statements, and which one actually gives the output will depend on flow control. For example:" ] }, { "cell_type": "code", "execution_count": 44, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def is_even(n):\n", " if n % 2 == 0:\n", " return True\n", " else:\n", " return False" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n" ] } ], "source": [ "print(is_even(6))\n", "print(is_even(37))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This output can be used anywhere in your code. For example, it can be set to a variable:" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n" ] } ], "source": [ "parity = is_even(8)\n", "print(parity)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or used anywhere else:" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "2\n", "4\n", "6\n", "8\n" ] } ], "source": [ "for x in range(10):\n", " if is_even(x):\n", " print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Your functions probably should return a value most of the time. Some programmers don't even consider an outputless function to be a function; instead, they will call it a \"procedure\".\n", "\n", "If you want to return more than one output, just seperate the outputs by commas. The output will be given as a tuple, which you can unpack into seperate variables if you like:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Foo', 'Bar')\n", "Foo\n", "Bar\n" ] } ], "source": [ "def two_outputs():\n", " return \"Foo\", \"Bar\"\n", "\n", "print(two_outputs())\n", "out1, out2 = two_outputs()\n", "print(out1)\n", "print(out2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Doc-strings\n", "\n", "As your programs get bigger, and especially if you collaborate with others on your program, making it clear what functions do becomes a bigger and bigger problem. Once you have written a function, you, or your collaborators will not necessarily want to have to read the code to remember how to use it. Mostly, they'll just want to know what inputs to give it, and what output to expect -- the computational details may be no longer relevant. For this purpose, we use a doc-string. A doc-string is a short \"help\" paragraph written just below the function's header, delimited by three quote marks. Let's give is_even() a doc-string. There are no hard rules, so long as it is clear." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def is_even(n):\n", " '''\n", " Input: an integer\n", " Ouput: a boolean\n", " Tells us whether a given integer is even.\n", " '''\n", " if n % 2 == 0:\n", " return True\n", " else:\n", " return False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This snippet can even be accessed \"introspectively\" (that is, a program reading its own source code) like so:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function is_even in module __main__:\n", "\n", "is_even(n)\n", " Input: an integer\n", " Ouput: a boolean\n", " Tells us whether a given integer is even.\n", "\n" ] } ], "source": [ "help(is_even)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Basically all functions in Python and its standard library have doc-strings, and writing good, clear doc-strings should become a habit." ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on built-in function max in module builtins:\n", "\n", "max(...)\n", " max(iterable, *[, default=obj, key=func]) -> value\n", " max(arg1, arg2, *args, *[, key=func]) -> value\n", " \n", " With a single iterable argument, return its biggest item. The\n", " default keyword-only argument specifies an object to return if\n", " the provided iterable is empty.\n", " With two or more arguments, return the largest argument.\n", "\n" ] } ], "source": [ "help(max) # example with the max function" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "A function can be given additional annotations, included in its docstring, via the following syntax in the header:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def is_even(n:int) -> bool: # <<< look here\n", " '''\n", " Tells us whether a given integer is even.\n", " '''\n", " if n % 2 == 0:\n", " return True\n", " else:\n", " return False" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The int and bool words are ignored by Python when running, but make it clear to the programmer what the input and output of the function will be. It then appears in the function's help file:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function is_even in module __main__:\n", "\n", "is_even(n:int) -> bool\n", " Tells us whether a given integer is even.\n", "\n" ] } ], "source": [ "help(is_even)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exercises and examples\n", "\n", "First we have some worked examples in this video. Of course, you are encouraged to paused the video before solutions are given to try and do it yourself." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", " \n", " " ], "text/plain": [ "" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.display import YouTubeVideo\n", "YouTubeVideo(\"1WCkAA4B2dE\")" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "### More exercises\n", "\n", "1. Rewrite your Fizzbuzz program (PurePy 2) with a function that returns either number, Fizz, Buzz, or Fizzbuzz. Then create a loop that repeats your function and then prints the result each time. This practice of taking a program and separating into out into a more organized system is called \"re-factoring\", and is an important, though often tedious, part of programming.\n", "2. " ] }, { "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.6.1" } }, "nbformat": 4, "nbformat_minor": 1 }