{ "metadata": { }, "nbformat": 4, "nbformat_minor": 5, "cells": [ { "id": "metadata", "cell_type": "markdown", "source": "
\n\n# Python - Flow Control\n\nby [The Carpentries](https://training.galaxyproject.org/hall-of-fame/carpentries/), [Helena Rasche](https://training.galaxyproject.org/hall-of-fame/hexylena/), [Donny Vrins](https://training.galaxyproject.org/hall-of-fame/dirowa/), [Bazante Sanders](https://training.galaxyproject.org/hall-of-fame/bazante1/)\n\nCC-BY licensed content from the [Galaxy Training Network](https://training.galaxyproject.org/)\n\n**Objectives**\n\n- How can my programs do different things based on data values?\n\n**Objectives**\n\n- Write conditional statements including if, elif, and else branches.\n- Correctly evaluate expressions containing and and or.\n\n**Time Estimation: 40M**\n
\n", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-0", "source": "

“Flow Control” is how we describe when we change the flow of code’s execution, based on some conditions. Here we’ll learn how to take different actions depending on what data out program sees, or how to run code only if some condition is true.

\n
\n

💬 Comment

\n

This tutorial is significantly based on the Carpentries Programming with Python and Plotting and Programming in Python, which are licensed CC-BY 4.0.

\n

Adaptations have been made to make this work better in a GTN/Galaxy environment.

\n
\n
\n

Agenda

\n

In this tutorial, we will cover:

\n
    \n
  1. Comparators
  2. \n
\n
\n

Comparators

\n

In Python we have the following comparators to do compare two values

\n\n

They’re all “binary” comparators, we can only compare two values at a time.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-1", "source": [ "print(37 < 38)\n", "print(38 < 38)\n", "print(39 < 38)" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-2", "source": "

These print out True or False, these are the two possible values of the boolean datatype in Python.

\n

We can use <= to check if it’s less than or equal to:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-3", "source": [ "print(19 <= 20)\n", "print(20 <= 20)\n", "print(21 <= 20)" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-4", "source": "

And we can use == for comparing numbers in Python

\n
print(11 == 11)\nprint(11 != 11)\nprint(22 != 33)\n
\n

And now that we can compare numbers, we can start doing useful things with them!

\n

Conditionals

\n

We can ask Python to take different actions, depending on a condition, with an if statement:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-5", "source": [ "num = 37\n", "if num > 100:\n", " print('greater')\n", "else:\n", " print('not greater')\n", "print('done')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-6", "source": "

The second line of this code uses the keyword if to tell Python that we want to make a choice.\nIf the test that follows the if statement is true,\nthe body of the if\n(i.e., the set of lines indented underneath it) is executed, and “greater” is printed.\nIf the test is false,\nthe body of the else is executed instead, and “not greater” is printed.\nOnly one or the other is ever executed before continuing on with program execution to print “done”:

\n

\"A

\n

Conditional statements don’t have to include an else. If there isn’t one,\nPython simply does nothing if the test is false:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-7", "source": [ "num = 53\n", "print('before conditional...')\n", "if num > 100:\n", " print(f'{num} is greater than 100')\n", "print('...after conditional')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-8", "source": "
\n

❓ Question: If behaviour

\n

Try changing the num value and see what happens for different values.

\n

What happens if num is a:

\n
    \n
  1. 202
  2. \n
  3. 3.145
  4. \n
  5. “test”
  6. \n
  7. 100.000001
  8. \n
\n
\n
Hint: Select the text with your mouse to see the answer

👁 Solution

\n
    \n
  1. Condition is activated!
  2. \n
  3. Nothing, but not because it is a float! Because it’s less than 100
  4. \n
  5. Traceback, a TypeError, you cannot compare strings with integers
  6. \n
  7. Condition is activated!
  8. \n
\n
\n
\n

Multiple Branches

\n

But what if you want more branches? What if you need to handle more cases? elif to the rescue!

\n

We can chain several tests together using elif, which is short for “else if”.

\n
if todays_temperature > 30:\n    print(\"Wear shorts! Remember your sunscreen\")\nelif todays_temperature > 20:\n    print(\"It's nice weather finally! Gasp!\")\nelif todays_temperature < 10:\n    print(\"Time to bundle up!\")\nelse:\n    print(\"Dress normally\")\n
\n
\n

💡 Tip: If/Elif/Elif/Elif/Else:

\n

if/elif/else cases follow these rules:

\n\n
\n

Each of these three sections is a branch, the code pauses, and chooses to go down one of the branches based on the conditions.

\n

The following Python code uses elif to print the sign of a number.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-9", "source": [ "num = -3\n", "\n", "if num > 0:\n", " print(f'{num} is positive')\n", "elif num == 0:\n", " print(f'{num} is zero')\n", "else:\n", " print(f'{num} is negative')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-10", "source": "

NB: To test for equality we use a double equals sign ==\nrather than a single equals sign = which is used to assign values.

\n

Combining Tests

\n

We can also combine tests using and and or.\nand is only true if both parts are true:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-11", "source": [ "a = 1\n", "b = -1\n", "if (a > 0) and (b <= 0):\n", " print('both parts are true')\n", "else:\n", " print('at least one part is false')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-12", "source": "
\n

❓ Question: Predict what happens

\n

Predict the outcomes of the following values of a and b above. Predicting what you think the code will do is a useful skill to practice

\n
    \n
  1. a = 0; b = -1
  2. \n
  3. a = 0; b = 10
  4. \n
  5. a = 4; b = -22
  6. \n
  7. a = 99; b = 99
  8. \n
\n
\n
Hint: Select the text with your mouse to see the answer

👁 Solution

\n
    \n
  1. at least one part is false
  2. \n
  3. at least one part is false
  4. \n
  5. both parts are true
  6. \n
  7. at least one part is false
  8. \n
\n
\n
\n

while or is true if at least one part is true:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-13", "source": [ "a = 1\n", "b = -1\n", "if (a < 0) or (b > 0):\n", " print('at least one test is true')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-14", "source": "
\n

💡 Tip: True and False

\n

True and False are special words in Python called booleans,\nwhich represent truth values. A statement such as 1 < 0 returns\nthe value False, while -1 < 0 returns the value True.

\n
\n

True and False booleans are not the only values in Python that are true and false.\nIn fact, any value can be used in an if or elif. This is commonly used to\ncheck, for instance, if a string is empty or if some data is provided:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-15", "source": [ "if '':\n", " print('empty string is true')\n", "if 'word':\n", " print('word is true')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-16", "source": "

You can also use it to check if a list is empty or full:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-17", "source": [ "if []:\n", " print('empty list is true')\n", "if [1, 2, 3]:\n", " print('non-empty list is true')\n", "# The last statement is equivalent to:\n", "if len([1, 2, 3]) > 0:\n", " print('non-empty list is true')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-18", "source": "

Or you can check if a number is zero, or non-zero:

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-19", "source": [ "if 0:\n", " print('zero is true')\n", "if 1:\n", " print('one is true')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-20", "source": "

Inverting Conditions

\n

Sometimes it is useful to check whether some condition is not true.\nThe Boolean operator not can do this explicitly.\nAfter reading and running the code below,\nwrite some if statements that use not to test the rule\nthat you formulated in the previous question.\nnot is a unary (not binary) operator: it only takes a single value

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-21", "source": [ "if not '':\n", " print('empty string is not true')\n", "if not 'word':\n", " print('word is not true')\n", "if not not True:\n", " print('not not True is true')" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-22", "source": "

Ranges

\n

Python makes it super easy to check if a number is within a range.

\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-23", "source": [ "quality_score = 32 # Try out different values!\n", "\n", "if quality_score > 40:\n", " print(\"Your data is a bit sus\")\n", "elif 20 < quality_score <= 40:\n", " print(\"Hey that looks ok\")\n", "elif 4 < quality_score <= 20:\n", " print(\"Oh you did nanopore sequencing\")\n", "else:\n", " print(\"It shouldn't be *that* bad. Try again.\")" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-24", "source": "

There are two important points here:

\n\n

Exercises

\n
\n

❓ Question:

\n

ifs, elifs and elses get evaluated in blocks. Look at the following code and list the lines that are part of a single block.

\n
1.  if x:\n2.      # ..\n3.  if y:\n4.      # ..\n5.  elif z:\n6.      # ..\n7.  if q:\n8.      # ..\n9.  else:\n10.     # ..\n11. elif t:\n12.     # ..\n13. else e:\n14.     # ..\n
\n
\n
Hint: Select the text with your mouse to see the answer

👁 Solution

\n

“Blocks” of if/elif/elses

\n\n

The above blocks are parsed together, you could not insert a print anywhere within the blocks, but between the blocks it would work.

\n\n
\n
\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-25", "source": [ "# Test code here." ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-26", "source": "
\n

❓ Question: How Many Paths?

\n

Consider this code:

\n
if 4 > 5:\n    print('A')\nelif 4 == 5:\n    print('B')\nelif 4 < 5:\n    print('C')\n
\n

Which of the following would be printed if you were to run this code?\nWhy did you pick this answer?

\n
    \n
  1. A
  2. \n
  3. B
  4. \n
  5. C
  6. \n
  7. B and C
  8. \n
\n
\n
Hint: Select the text with your mouse to see the answer

👁 Solution

\n

C gets printed because the first two conditions, 4 > 5 and 4 == 5, are not true,\nbut 4 < 5 is true.

\n
\n
\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-27", "source": [ "# Test code here." ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-28", "source": "
\n

❓ Question: Close Enough

\n

Write some conditions that print True if the variable a is within 10 of the variable b\nand False otherwise.\nCompare your implementation with your partner’s:\ndo you get the same answer for all possible pairs of numbers?

\n
\n

💡 Tip: abs

\n

There is a [built-in function abs][abs-function] that returns the absolute value of\na number:

\n
print(abs(-12))\n
\n
12\n
\n
\n
\n

👁 Solution 1

\n
a = 5\nb = 5.1\n\nif abs(a - b) <= 10:\n    print('True')\nelse:\n    print('False')\n
\n
\n
\n

👁 Solution 2

\n
print(abs(a - b) <= 10)\n
\n

This works because the Booleans True and False\nhave string representations which can be printed.

\n
\n
\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-29", "source": [ "# Test code here." ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-30", "source": "
\n

❓ Question: Pitfalls

\n

A integer number between 0 and 100 will be provided to this function. Answer these two questions:

\n\n
num = 42 # Randomly chosen so the code will execute, try changing it around.\nif num > 90:\n    print(\"great score\")\nelif num < 32:\n    print(\"Very cold\")\nelif num >= 86:\n    print(\"Almost\")\nelif num == 86:\n    print(\"It's exactly this value!\")\nelif 32 < num < 58:\n    print(\"Getting warmer\")\nelif 57 < num <= 86:\n    print(\"Everything else goes here\")\n
\n
\n
Hint: Select the text with your mouse to see the answer

👁 Solution

\n
    \n
  1. No, it won’t. 32 is the only value there that doesn’t print anything. You can either do x < 57 and later 57 <= x to test the bigger and smaller values, or you can make use x < 57 and 56 < x, which have the same results, but only with integers. If your code accepted a float, e.g. 56.5, both of those tests would be true. So x < 57 and later 57 <= x is the preferred way to write that.
  2. \n
  3. 86 is the most obvious solution to this, the programmer added a check specifically to see if the value was 86, but instead it’s caught by the previous case.
  4. \n
\n
\n
\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "id": "cell-31", "source": [ "num = 42 # Randomly chosen so the code will execute, try changing it around.\n", "if num > 90:\n", " print(\"great score\")\n", "elif num < 32:\n", " print(\"Very cold\")\n", "elif num >= 86:\n", " print(\"Almost\")\n", "elif num == 86:\n", " print(\"It's exactly this value!\")\n", "elif 32 < num < 58:\n", " print(\"Getting warmer\")\n", "elif 57 < num <= 86:\n", " print(\"Everything else goes here\")" ], "cell_type": "code", "execution_count": null, "outputs": [ ], "metadata": { "attributes": { "classes": [ "python" ], "id": "" } } }, { "id": "cell-32", "source": "
\n

💡 Tip: Why a synthetic example like this?

\n

Complicated if/elif/else cases are common in code, you need to be able to spot these sort of issues. For example there are large if/else cases in the Galaxy codebase, sometimes nested even, and being ale to predict their behaviour is really important to being able to work with the code. Missing else cases are sometimes important, sometimes a bug, sometimes just the code hasn’t been implemented yet, which is why we always write good code comments!

\n
\n", "cell_type": "markdown", "metadata": { "editable": false, "collapsed": false } }, { "cell_type": "markdown", "id": "final-ending-cell", "metadata": { "editable": false, "collapsed": false }, "source": [ "# Key Points\n\n", "- Use `if condition` to start a conditional statement, `elif condition` to provide additional tests, and `else` to provide a default.\n", "- The bodies of the branches of conditional statements must be indented.\n", "- Use `==` to test for equality.\n", "- `X and Y` is only true if both `X` and `Y` are true.\n", "- `X or Y` is true if either `X` or `Y`, or both, are true.\n", "- Zero, the empty string, and the empty list are considered false; all other numbers, strings, and lists are considered true.\n", "- `True` and `False` represent truth values.\n", "- `not` can be used to invert the condition\n", "\n# Congratulations on successfully completing this tutorial!\n\n", "Please [fill out the feedback on the GTN website](https://training.galaxyproject.org/training-material/topics/data-science/tutorials/python-flow/tutorial.html#feedback) and check there for further resources!\n" ] } ] }