{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Structuring code\n", "\n", "In the previous lesson you learned about basic object types (`bool`,`int`,`float`,`str`,`tuple`,`list`,`dict`). \n", "In this lesson we will discuss 3 different concepts, which help structure python code as well as control the code flow, i.e. which code is executed and under which conditions:\n", "* functions\n", "* conditional statement: `if`, `else` and `ifel`\n", "* loops: `for` and `while`\n", "\n", "All three rely on the concept of **code block**. In python, a code block:\n", "* starts after a line that ends with the colon operator `:`.\n", "* is delimited by spaces or indentation *(in other langguages such as C or R, this corresponds to {})*.\n", "* can contain other code blocks.\n", "* defines a **namespace**.\n", "\n", "> Regardless of whether you use four spaces or a tab, it is important to be consistent!\n", "\n", "A **namespace**, in python, is basically a system to make sure that all the names in a program are unique and can be used without conflict. \n", "For the user, this means that any variable defined in a block can only be accessed by this block or a block inside of it. \n", "For instance, one usually cannot access objects created inside a function from outside this function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Functions\n", "In the first lesson, you already used a few of python's inbuilt functions: `help()`,`print()`,`len()`, ... , as well as some objects methods, which are functions as well. \n", "While it is always best to use python's inbuilt functions when available, it is often really useful to be able to write your own functions!\n", "\n", "In python, functions are declared using the `def` keyword, followed by the named of the function, brakets `()` where arguments can be specified, and finally a column `:` character. \n", "Let's see a first example of a really simple function that does not take any arguments:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "greetings.\n" ] } ], "source": [ "def greetings():\n", " print(\"greetings.\")\n", "\n", "# Let's try to call our new function.\n", "greetings()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is a variation of this function, with an **argument** added. An **argument** is a value that is passed to the function and can make its behavior change." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "greetings,Bob.\n", "greetings,Alice.\n" ] } ], "source": [ "def greetings_personalised(name):\n", " print(\"greetings,\" + name + \".\")\n", "\n", "# Let's try to call our new function with different argument values.\n", "greetings_personalised(\"Bob\")\n", "greetings_personalised(\"Alice\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following are the crucial parts of a function:\n", "* its name.\n", "* its arguments: what it receives from the world outside of the function.\n", "* its documentation: defined by adding triple-quoted text at the beginning of the function block. This text is called a **docstring** and constitutes the function's documentation. This is what appears when you use the `help()` on this function. \n", "* the code inside the function.\n", "* its return value: what the outside world gets from the function. It is specified in the function using the `return` keyword.\n", "\n", "Take for instance this function:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def multiply(argument1, argument2):\n", " \"\"\" \n", " This function multiplies argument1 and argument2 \n", " together and then returns the result.\n", " \"\"\"\n", " print('Argument 1 is' , argument1 )\n", " print('Argument 2 is' , argument2 )\n", " result = argument1 * argument2\n", " return result" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* its **name** is \"multiply\".\n", "* its **arguments** are \"argument1\" and \"argument2\".\n", "* its **docstring** is the `\"\"\"` text just after the `def` line: this is what appears when you use the `help()` on the function. It is good practice to document your functions.\n", "* its **return value** is the content of the \"result\" variable." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on function multiply in module __main__:\n", "\n", "multiply(argument1, argument2)\n", " This function multiplies argument1 and argument2 \n", " together and then returns the result.\n", "\n" ] } ], "source": [ "help(multiply)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A function's arguments can be called by order (i.e., the first argument in first position and so forth), or using their name explicitely (in which case the order does not matter)." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Argument 1 is 12\n", "Argument 2 is 47\n", "The multiplication result is: 564 \n", "\n", "Argument 1 is 12\n", "Argument 2 is 47\n", "The multiplication result is: 564 \n", "\n" ] } ], "source": [ "result = multiply(12, 47)\n", "print(\"The multiplication result is:\", result, \"\\n\")\n", "\n", "result = multiply(argument2=47, argument1=12)\n", "print(\"The multiplication result is:\", result, \"\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `return` statement is what makes us able to get something from the function:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Argument 1 is 12\n", "Argument 2 is 47\n", "The inverse of the result is: 0.0017730496453900709\n" ] } ], "source": [ "r = multiply(12, 47) # I redirect the output value of the function to variable r\n", "# I am now free to manipulate r:\n", "print('The inverse of the result is:', 1 / r)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "### Beware of namespaces" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "function can access x: 5\n", "function has variable y: 5\n" ] }, { "ename": "NameError", "evalue": "name 'y' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;31m# Let's now try to access y outside of the function....\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 10\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Can I access y outside of the function? Its value is:'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'y' is not defined" ] } ], "source": [ "def function():\n", " print(\"function can access x:\", x)\n", " y = 5\n", " print(\"function has variable y:\", y)\n", "\n", "x = 5 # I create variable \"x\" in the basic namespace, outside of the function.\n", "function() # calling function : it works.\n", "\n", "# Let's now try to access y outside of the function....\n", "print('Can I access y outside of the function? Its value is:', y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What happens is that the function can access `x`, because it was created in a block that contains the function. \n", "However, `y` was defined inside of the function's, and therefore is restricted to the functions's namespace: it cannot be accessed from outside the function.\n", "\n", "> Although it is possible, it is generally considered bad practice to access variables that were created outside a function from inside a function. \n", " Instead, one should use the arguments to \"pass\" values to functions. \n", " The reason for this is that it makes code more error prone and harder to debug or reuse: if a function depends on its context, then I cannot simply copy/paste it to into another code...\n", ".\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Micro exercise** : write a function that takes a number and returns its square (for example, if you give it 12 it should return 144)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Conditional statements : `if` , `elif`, `else`\n", "There are several ways to control the flow of your code using logical statements.\n", "* The `if` keyword followed by an expression defines a block that will be executed only if the given statement is `True`.\n", "* The `else` keyword defines a block to be executed if the previous `if` or `elif` expressions were `False`.\n", "* Tests for additional conditions can be added using the `elif` keyword (contraction of `else if`).\n" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "this is an adult\n" ] } ], "source": [ "age = 25\n", "\n", "if age >= 18: # first statement.\n", " print('this is an adult')\n", "elif age >= 0: # this statement will be tested if the previous one was False.\n", " print('this is a child')\n", "else: # this statement will be used if all previous one were False.\n", " print('age is negative?')\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`if` statements are built principaly using **comparison operators**, which you have seen in the previous lesson : \n", " `==`,`>`,`<`,`>=`,`<=`,`!=`.\n", "\n", "conditions may be further combined using **logical operators** :\n", "* `and` : combines 2 statements and returns `True` if both are `True`\n", "* `or` : combines 2 statements and returns `True` if at least one is `True`\n", "* `in` : returns `True` if the element to its left is found inside the container to its right\n", "* `not` : inverts a `True` to `False` and vice-versa\n", "* `is` : returns `True` if two variable reference the same object **(but we have not talked about this yet...)**\n" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "at least 1 condition satisfied.\n", "5 is absent from [7, 125, 48, 52, 2, 22, 1]\n", "inverted condition satisfied.\n", "did you follow this?\n", "could you set a and/or b so that the whole expression becomes False?\n" ] } ], "source": [ "a = 5\n", "b = 10\n", "l = [7, 125, 48, 52, 2, 22, 1]\n", "\n", "if a > 0 and b < 10 :\n", " print('both conditions satisfied.')\n", "\n", "if a > 0 or b < 10 :\n", " print('at least 1 condition satisfied.')\n", "\n", "if a in l :\n", " print(a,'is in',l)\n", "else:\n", " print(a,'is absent from',l)\n", "\n", "if not a > 10 :\n", " print('inverted condition satisfied.')\n", "\n", "# Of course they can be combined :\n", "if not a in l or ( a > 0 and not b in l ) :\n", " print('did you follow this?')\n", " print('could you set a and/or b so that the whole expression becomes False?')\n", "else:\n", " print('success')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Micro exercise** : In the above code, could you set a and/or b so that the whole expression becomes False? (success should be printed when you execute the code cell)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# `while` loops\n", "\n", "A **loop** is a block of code which will be repeated a certain number of time. \n", "Similar to the `if` keyword, the `while` keyword followed by an expression defines a block that is executed only if the given statement is `True`.\n", "\n", "The difference is that at the end of the block the `while` statement is evaluated again, and if it is still `True` then the block gets executed again." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "counter: 0\n", "counter: 1\n", "counter: 2\n", "counter: 3\n", "counter: 4\n", "counter: 5\n", "counter: 6\n", "counter: 7\n", "counter: 8\n", "counter: 9\n" ] } ], "source": [ "# This is perhaps the most typical while loop:\n", "i = 0 # Initialize a counter\n", "while i < 10 : # While the counter is less than 10, continue\n", " print('counter: ',i)\n", " i += 1 # Increment the counter : DO NOT FORGET THAT LINE or the loop becomes infinite!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Loops are often used to populate a container objects such as a list:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n" ] } ], "source": [ "i = 0 # Initialize a counter\n", "squares = []\n", "while i < 10 : \n", " squares.append(i**2)\n", " i += 1 \n", "print(squares)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you forget to increment your counter (or whatever thing you test for in the while loop), then you will face the **infinite loop**. Your only solution then is to stop the code execution: \n", "* Jupyter : click on the \"interrupt the kernel\" square button at the top of the window.\n", "* Linux or MacOS console : `Ctrl-C`.\n", "* Windows console : `Ctrl-Break` or `Ctrl-Alt-Esc`, then find your process and kill it.\n", "\n", "Infinite loops can get particularly nasty if you also allocate memory within the loop (as we are doing when we `append` values to a list), as your program will start hogging all the memory from the machine. It will eventually be killed, but this may slow down, or even freeze, your computer for some time..." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "stuck in infinite loop...\n", "etc...\n", "\n" ] } ], "source": [ "i = 0\n", "x = 5\n", "while i < 10 :\n", " x += 1\n", " print('stuck in infinite loop...')\n", " \n", " # Ignore this part for now, it's only to stop the loop from printing too many lines.\n", " # Well, it's not longer infinite... but you get the idea.\n", " if x == 20:\n", " print('etc...\\n')\n", " break" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## `for` loops\n", "Instead of testing an expression to decide whether to continue executing a block, \n", "`for` loops iterate over all elements in an iterable object (*i.e.*, a container).\n", "\n", "The `for` keyword is followed by a statement of the form : `x in y`, where `y` is the iterable and `x` is the variable that will successively contain the elements of `y` during the loop execution. \n", "They are very useful when one wants to perform a specific task on all elements of a list, for instance." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "element: 1\n", "element: 47\n", "element: 59\n", "element: 59\n" ] } ], "source": [ "# Loop that iterates over a list and prints each of its elements.\n", "myList = [1 , 47 , 59 , 59]\n", "for e in myList:\n", " print('element:', e)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "a 34\n", "b 26\n", "c 456\n" ] } ], "source": [ "# For loops can also be used to iterate over a dictionnary's keys.\n", "myDict = {'a':34 , 'b':26 , 'c':456}\n", "for key in myDict:\n", " print(key , myDict[key])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Micro exercise** : \n", "1. Use a `while` loop to create a list containing the multiples of 13 that are under 100. \n", "2. Then use a `for` loop to go though this list and print its elements." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "### `for` loop tricks: using the `range()` function\n", "The `range()` function takes 1 to 3 integer arguments:\n", "* `start`: optional agument, by default it is `0`.\n", "* `stop`: the only non optional argument.\n", "* `step`: optional argument, by default it is `1`.\n", "\n", "It return an **iterator** that **yields** integers from `start` (included) to `stop` (excluded) in increments of `step`.\n", "The `range()` function is typically used to iterate over all indices in a list, as shown in the example below. \n", "*Note:* we have no seen **iterators** yet, but essentially you can consider them as functions that produce a finite series of values that can be iterated over." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "index = 0 : value = 1\n", "index = 1 : value = 47\n", "index = 2 : value = 59\n", "index = 3 : value = 59\n" ] } ], "source": [ "myList = [1 , 47 , 59 , 59]\n", "listSize = len(myList)\n", "for i in range(listSize):\n", " print('index =', i , ': value =', myList[i])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "### `for` loop tricks: using the `enumerate()` function\n", "The `enumerate()` function takes an iterable as argument and creates Tuples of `(index, value)` for all elements in the iterable.\n", "This can be useful when one needs to access both the element and its index in a list." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "element 1 is at index 0\n", "element 47 is at index 1\n", "element 59 is at index 2\n", "element 59 is at index 3\n", "element 1 is at index 0\n", "element 47 is at index 1\n", "element 59 is at index 2\n", "element 59 is at index 3\n" ] } ], "source": [ "# Without enumerate(), we would need to do something like:\n", "index = 0\n", "for element in myList:\n", " print('element' , element , 'is at index' , index)\n", " index += 1\n", "\n", "# Thanks to the enumarate() function, we can rewrite this in a more efficient manner:\n", "for index, element in enumerate(myList):\n", " print('element', element, 'is at index', index)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## loop control : `break` and `continue`\n", "These two keywords can help you control the flow of your loops:\n", "* `break`: exits the current loop block.\n", "* `continue`: skips the rest of the current iteration of the loop block to the beginning of the next iteration." ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The first vowel is: i\n", "1\n", "3\n", "The fraction of vowels is: 0.3333333333333333\n" ] } ], "source": [ "sentence = \"This is a sentence. This is another sentence\"\n", "\n", "# Loop to find the first vowel of the sentence:\n", "firstVowel = ''\n", "for c in sentence: # Remember that strings are sequences of letters, so they can be iterated over.\n", " if c in 'aeiouy': # Test if letter is a vowel.\n", " firstVowel = c\n", " break # Break after first vowel is found, to avoid testing all other letters.\n", "print(\"The first vowel is:\", firstVowel)\n", "\n", "# Loop to compute the fraction of vowels in the sentence, but only among letters (i.e., ignoring spaces and punctuations).\n", "nbLetters = 0\n", "nbVowels = 0\n", "for c in sentence: \n", " if not c.isalpha(): # Test if the character is an letter or not.\n", " continue # If it is not, skip the rest of the current iteration of the loop.\n", " \n", " nbLetters += 1 # Increment the counter.\n", " if c in 'aeiouy': # Test if it is a vowel\n", " nbVowels += 1\n", "\n", "print(\"The fraction of vowels is:\" , nbVowels / nbLetters )\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In many cases, it could be argued that a `break` or a `continue` could be replaced by an `if ... else` structure or a different loop. \n", "Choose one option or the other depending on what seems to make sense to you and leads to clear, tidy and easy-to-understand code.\n", "\n", "\n", "> It is up to developpers to write their code so that it **performs properly** but is also **as easy as possible to understand, maintain, and extend**.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## Exercises: 2.1 - 2.7" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "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.7.4" } }, "nbformat": 4, "nbformat_minor": 4 }