{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 1. Advanced loops & Advanced functions\n", "\n", "Welcome back. Before we start this lesson, we'll have a quick recap of what we learnt last year. A program is composed of the following components:\n", "\n", "* An algorithm that the program implements\n", "\n", "* Variables\n", "\n", "* Control flow - if/elif, while, for\n", "\n", "* Functions/Methods\n", "\n", "* Libraries\n", "\n", "This year, we'll be building on these ideas and also learning new components, expanding our toolbox, allowing us to do more for (hopefully) less code. We'll be covering the following topics, in the following order:\n", "\n", "* Advanced loops\n", "\n", "* Advanced functions\n", "\n", "* Advanced algorithms\n", "\n", "* Introduction to Object Oriented Programming\n", "\n", "* Making your own libraries/modules\n", "\n", "As you can see, we'll be building on everything we learnt last year. We'll be doing quick recaps at the start of every lecture, but if you don't remember anything, it would probably be a good idea to look over before the respective lectures.\n", "\n", "There'll also be some additional, optional topics that you can look at if you're interested. It is recommended you look at these after the main course is finished:\n", "\n", "* Lambda functions, functions as variables, and decorators\n", "\n", "* Common mistakes in python\n", "\n", "* Creating Animations\n", "\n", "* Structuring your Program\n", "\n", "* Debugging in Python\n", "\n", "* How to Version Control 1.0.0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Advanced loops\n", "\n", "Hopefully you remember the while and for loops we learnt last year. Here are the same examples from last year to rejig your memory:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "10\n" ] } ], "source": [ "x = 0\n", "while x <= 10:\n", " print(x)\n", " x += 1" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n", "10\n" ] } ], "source": [ "for x in range(0, 11):\n", " # range(0, 11) means that the for loop iterates between 0 and 10\n", " print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The type of loop we'll be learning today, list comprehensions, are a syntactic sugar (a nicer way to write something) to replace for loops. They allow for great, succint code. Let's look at an example with a for loop and then with a list comprehension, and see how they compare." ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979, 2.449489742783178, 2.6457513110645907, 2.8284271247461903, 3.0]\n" ] } ], "source": [ "from math import sqrt\n", "a = []\n", "for x in range(-10,10):\n", " if x > 0:\n", " a.append(sqrt(x))\n", "print(a)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979, 2.449489742783178, 2.6457513110645907, 2.8284271247461903, 3.0]\n" ] } ], "source": [ "from math import sqrt\n", "a = [sqrt(x) for x in range(-10,10) if x > 0]\n", "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the list comprehension version of the same code is a lot shorter. Although the difference may seem insignificant in this case, it can result in shorter, beautiful, more readable code in more complex cases.\n", "\n", "The general syntax is:\n", " \n", "result = [*some statement* for *element* in *list* if *condition*]\n", "\n", "With some statement being any valid piece of python code. For example, we can do the following to replace the example for for loops that we encountered last year:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "8\n", "9\n" ] } ], "source": [ "__ = [print(x) for x in range(0,10)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this case, we assigned the result of the list comprehension to \\_\\_ because we weren't interested in the output (which would have been a list of None s), but rather on the action performed by the list comprehension.\n", "\n", "Try using list comprehensions to make a list of the first n fibonacci numbers:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Advanced functions\n", "\n", "#### Default Arguments\n", "\n", "Sometimes, we don't want to pass to a function all of the parameters. You may have already seen this in things like numpy curve_fit. It allows you to fit an arbitary function to a data set. Most likely, if you've used it, you've only supplied the function you want to fit, and the data set. However, there are a number of other parameters that you can optionally set, such as initial guesses for the parameters, and the method used for the fitting. The designers of the function realised that 90% of the time, you don't need to set those parameters yourself, and made the function in such a way that you only need to supply the bare minimum you need to use the function. We can do that too using default arguments, as shown in the cell below:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3, 3]\n", "[3]\n" ] } ], "source": [ "def append_3(list_=None):\n", " if list_ is None:\n", " list_ = []\n", " list_.append(3)\n", " return list_\n", "\n", "list1 = [3]\n", "append_3(list_=list1)\n", "list2 = append_3()\n", "print(list1)\n", "print(list2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Default arguments take the following syntax:\n", "\n", "def function_name(*required arguments*, *arguments with default values*)\n", "\n", "With default arguments, a lot of the time, we will only be referring to some of them, so it is good practice to name them when we use them.\n", "\n", "Notice that instead of using the more intuitive option of list\\_ = [ ], we said list\\_ = None. This is because the following would happen if we did it that way:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "list1 is: [3]\n", "list 2 is: [3, 3]\n", "And list 1 is now: [3, 3]\n" ] } ], "source": [ "def append_3(list_=[]):\n", " list_.append(3)\n", " return list_\n", "\n", "list1 = append_3()\n", "print(\"list1 is:\", list1)\n", "list2 = append_3()\n", "print(\"list 2 is:\", list2)\n", "print(\"And list 1 is now:\", list1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This may seem counterintuitive, but it is because list\\_ isn't the object itself, but a reference to the object, as are list1 and list2. Therefore, when we append to [ ], we append to list\\_, list1, and list2, resulting in the counterintuitive behaviour. This does not happen with some other types such as integers, where when we add an integer to another, we aren't changing the integer but rather are creating a new one. If we stick to those, default arguments can be written in a naïve manner, but sometimes, we will have to employ the trick of argument=None so that we can have default arguments which are lists.\n", "\n", "#### A variable number of (Keyword) arguments\n", "\n", "We can also have our function accept an arbitary number of named or unnamed arguments, as shown below:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 2, 3, 4, 5, 6, 7)\n", "28.0\n" ] } ], "source": [ "def add_up(*args):\n", " total = 0.\n", " print(args)\n", " for arg in args:\n", " total += arg\n", " return total\n", "\n", "total = add_up(1,2,3,4,5,6,7)\n", "print(total)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is done by having a function have an argument which is preceded by a \\*. This argument will be stored as a tuple (a data structure that is similar to a list, but is immutable, i.e., it is read only. They are declared by the same syntax as lists, but with round brackets: (element1, element2)) \n", "\n", "In addition to these, we can also have a variable number of keyword arguments:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hongeohoe :\t 75.0\n", "Surströmming :\t 10.0\n", "Total:\t\t 85.0\n", "Service charge:\t 12.75\n" ] } ], "source": [ "def show_bill(**kwargs):\n", " total = 0.\n", " keys = sorted(kwargs.keys())\n", " service_charge = None\n", " for key in keys:\n", " if key != \"service_charge\":\n", " print(key, \":\\t\", kwargs[key])\n", " total += kwargs[key]\n", " else:\n", " service_charge = kwargs[key]\n", " print(\"Total:\\t\\t\", total)\n", " if service_charge:\n", " print(\"Service charge:\\t\", total * service_charge)\n", "\n", "show_bill(Surströmming = 10.00, Hongeohoe = 75.00, service_charge = 0.15)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, it works very similarly to having a variable number of unnamed arguments. Variable length keyword arguments are also sometimes used in code where there are many optional arguments (It is especially common in matplotlib code), as it allows for cleaner code, and the logic for assigning the arguments to their respective arguments can be performed later on. Documentation is of special importance in such cases, as without documentation, we'd have to read perhaps the entire function to find out what arguments can be passed to the function. The keyword arguments are passed as a dictionary. \n", "\n", "Although we have written \\*\\*kwargs and \\*args in these cases, and it is convention to do so, you can give them more descriptive names, as long as variable length keyword arguments are preceded by \\*\\* and varaible length unnamed arguments are preceded by \\*.\n", "\n", "You can also mix these special arguments with normal arguments we're familiar with, but in this case, we must do it in the foloowing order:\n", "\n", "def function_name(*arguments with no default arguments*, *arguments with default arguments*, * \\*variable number of unnamed arguments*, *\\*\\* variable number of named arguments*):\n", "\n", "Another useful thing functions can do is they can return multiple values as a tuple:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A String\n" ] } ], "source": [ "def return_multiple_values():\n", " return (\"A String\",12)\n", "\n", "x, y = return_multiple_values()\n", "print(x)" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "Using some of these techniques, write a function that outputs the first n primes as a string, or outputs the first 100 primes if no n is given." ] }, { "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 }