Branching#

Midjourney: Red or Blue Pill, ref. Leonardo da Vinci

When you come to a fork in the road, take it.

— Yogi Berra

Slides/PDF#

Conditional branching#

A computer often has to decide how information should be processed. To do this, the program must be able to execute certain statements or parts of the program only under certain conditions. For this purpose, all programming languages provide the concept of branching.

The simplest form of branching is conditional branching, which executes a block of code only when a logical condition is met. In Python, the if statement (If-Then) is provided for this purpose, which precedes a statement that is executed only if the condition is met. It is common to write the statements on a new line. Then they must be indented.

condition = True
if condition:
    # will only execute if condition is True
    print(f"Der Block mit 'bedingung={condition}' wurde ausgeführt. 1")

condition = False
if condition:
    # will only execute if condition is True
    print(f"Der Block mit 'bedingung={condition}' wurde ausgeführt. 2")
Der Block mit 'bedingung=True' wurde ausgeführt. 1

With the logical operator not, you can negate the conditions and combine them with and, or, and parentheses.

condition = True
if not condition:
    # is only executed when the condition is True
    print(f"Der Block mit 'bedingung={condition}' wurde ausgeführt.")

condition = 0
if condition:
    # is only executed if the condition is equivalent to True
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")

condition = 2
if condition:
    # is only executed if the condition is equivalent to True
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")

if not condition:
    # is only executed when the condition is True
    print(f"Der Block mit 'bedingung={condition}' wurde ausgeführt.")
Der Block mit 'bedingung'=2 wurde ausgeführt.

Strings that are empty and have length 0 correspond to the boolean value False, and non-empty strings correspond to the boolean value True.

condition = ''  # empty string
if condition:
    # will only execute when the condition evaluates to True
    print(f"The block with 'condition'={condition} was executed.")

condition = ' '
if condition:
    # will only execute when the condition evaluates to True
    print(f"The block with 'condition'={condition} was executed.")
The block with 'condition'=  was executed.

The same applies to lists and dictionaries when they are empty (False) or filled (True).

condition = []  # empty list
if condition:
    # will only be executed when the condition evaluates to True
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")

condition = ['nicht_leer']  # non-empty list
if condition:
    # will only be executed when the condition evaluates to True
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")
Der Block mit 'bedingung'=['nicht_leer'] wurde ausgeführt.

That makes it easy to quickly check in code whether, for example, a list already contains elements and whether they should be processed further.

For more complex data types, such as objects that can take the value None, this value is interpreted as False, and everything else as True, which in turn lets you check whether the variable has already been set.

condition=None  # not set
if condition:
    # will only execute if the condition is truthy
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")

condition="nicht_None"  # set
if condition:
    # will only execute if the condition is truthy
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")

condition=None  # not set
if condition is not None:
    # will only execute if the condition is truthy
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")

condition="nicht_None"  # set
if condition is not None:
    # will only execute if the condition is truthy
    print(f"Der Block mit 'bedingung'={condition} wurde ausgeführt.")
Der Block mit 'bedingung'=nicht_None wurde ausgeführt.
Der Block mit 'bedingung'=nicht_None wurde ausgeführt.

The Alternative#

It often happens that not only something should be executed when the condition is true, but in the other case something should also be executed. For this there is the else statement, which may only follow an if and, when there are multiple lines, must be indented at the same level as the if it belongs to.

condition = True
if condition:
    # is only executed when Condition is True
    print(f"Der Block mit 'bedingung={condition}' wurde ausgeführt.")
else:
    # is only executed when Condition is False
    print(f"Der alternative Block mit 'bedingung={condition}' wurde ausgeführt.")
Der Block mit 'bedingung=True' wurde ausgeführt.
condition = False
if condition:
    # is only executed when the condition is True
    print(f"Der Block mit 'condition={condition}' wurde ausgeführt.")
else:
    # is only executed when the condition is False
    print(f"Der alternative Block mit 'condition={condition}' wurde ausgeführt.")
Der alternative Block mit 'condition=False' wurde ausgeführt.

A typical example is catching division by zero. In computations you cannot always guarantee that the denominator will not be zero. Since division by zero is undefined, it can lead to a program error and, possibly, a program crash.

numerator = 5
denominator = 0
result = numerator / denominator
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[8], line 3
      1 numerator = 5
      2 denominator = 0
----> 3 result = numerator / denominator

ZeroDivisionError: division by zero

To handle this, we can use an if-else branch and assign the result the value None (in the sense of ‘undefined’).

denominator = 0
if denominator:  # the truth value of an int is False for 0 and True for values not equal to 0
	result = numerator / denominator
else:
	print("Teilung durch 0")
	result = None
print(f"Das Ergebnis von {numerator}/{denominator} = {result}")
Teilung durch 0
Das Ergebnis von 5/0 = None

Since, with Python’s dynamic typing, you cannot guarantee that zaehler and nenner are of the correct data type int, it makes sense to add a type check. For this, we first check the data type with the built-in function type() before performing the zero-division check. For this, we nest the if-else conditions.

numerator = 'keine_zahl'
denominator = 0
if type(numerator) == int:
    if type(denominator) == int:
        if denominator:  # the truth value of an int is False for 0 and True for != 0
            result = numerator / denominator
        else:
            print("Division by zero")
            result = None
    else:
        print(f"Denominator not of type `int`, but of type {type(denominator)}")
        result = None
else:
    print(f"Numerator not of type `int`, but of type {type(numerator)}")
    result = None

print(f"The result of {numerator}/{denominator} = {result}")
Numerator not of type `int`, but of type <class 'str'>
The result of keine_zahl/0 = None

Branching Alternatives#

If there are multiple nested alternatives as in the last example, the code quickly becomes hard to read and you need a more compact syntax. For this, Python has the elif statement, which is simply a shorthand for else if and does not require any further indentation. This way, you can write the previous code more readably as:

numerator = 'keine_zahl'
denominator = 0
result = None
if not isinstance(denominator, int):
    print(f"Nenner nicht vom Datentyp `int`, sondern vom Typ {type(denominator)}")
elif not isinstance(numerator, int):
    print(f"Zaehler nicht vom Datentyp `int`, sondern vom Typ {type(numerator)}")
elif not denominator:
    print("Teilung durch 0")
else:
    result = numerator / denominator

print(f"Das Ergebnis von {numerator}/{denominator} = {result}")
Zaehler nicht vom Datentyp `int`, sondern vom Typ <class 'str'>
Das Ergebnis von keine_zahl/0 = None

Here, we have:

  • first, the condition type(zaehler) == int replaced by the standard function isinstance(zaehler, int) which is identical and easier to read.

  • we have addressed all error cases first, to ensure they are checked before executing the calculation

  • thus we first check whether the zaehler is not of type int and in this case output an error message

  • if that is not the case, then nenner could not be of type int and we would in this case output a different error message

  • then the nenner must be of type int and we check whether it is 0 with an appropriate error message

  • since in all three cases the ergebnis has the value None, we set this assignment before the if block

  • the last else block is only reached if none of this applies, i.e., zaehler and nenner are of type int and nenner is not 0. Only in this case is ergebnis assigned the value of the division

ℹ️ Info: Here it should be noted that we deliberately moved the error message "Division by zero" to the end. It would also be possible to start with the condition `if not nenner` in the original order. However, there is a risk that, due to the truth values of other data types, e.g., an empty list, this condition would be satisfied. We would therefore get an incorrect error message displayed ("Division by zero" instead of "denominator of type `int`"), as shown in the following example. This is one of the typical dangers of dynamically typed languages. Therefore, type checks should always be performed first.
numerator = 10
denominator = []
result = None
if not denominator:
    print("Teilung durch 0")
elif not isinstance(denominator, int):
    print(f"Nenner nicht vom Datentyp `int`, sondern vom Typ {type(denominator)}")
elif not isinstance(numerator, int):
    print(f"Zaehler nicht vom Datentyp `int`, sondern vom Typ {type(numerator)}")
else:
    result = numerator / denominator

print(f"Das Ergebnis von {numerator}/{denominator} = {result}")
Teilung durch 0
Das Ergebnis von 10/[] = None

Many Alternatives#

In Python 3.10, the match statement was introduced, which already exists in many other languages. It allows you to compare a variable against several possible values.

Traditionally (many elifs):

if Variable == ValueA:
    StatementA
elif Variable == ValueB:
    StatementB
else:
    StatementC

New (match-case):

match Variable:
    case ValueA:
        StatementA
    case ValueB:
        StatementB
    case _:
        StatementC

Quiz#

--- shuffleQuestions: true shuffleAnswers: true --- ### What does an `if` statement do in Python? - [x] Executes a block when the condition is true - [ ] Repeats code multiple times - [ ] Terminates the program - [ ] Prompts the user for input ### Which syntax is correct for an `if` condition in Python? - [x] if x > 5: - [ ] if (x > 5) - [ ] if x > 5 then - [ ] if x > 5 {} ### What happens with the condition `if not x:` in Python? - [x] The code block is executed when `x` is interpreted as False - [ ] The code block is executed for `x == True` - [ ] The variable `x` is deleted - [ ] Python raises an error ### Which of these values count as `False` in Python? - [x] Empty list `[]` - [x] The value `None` - [x] Empty string `""` - [ ] The number `1` ### When is an `else` block executed in Python? - [x] When the `if` condition does not apply - [ ] When the `if` condition is true - [ ] Always when an error occurs - [ ] Immediately after the program starts ### What is `else` commonly used for? - [x] To execute alternative code - [ ] To define functions - [ ] To delete variables - [ ] To check indentation ### What is `elif` in Python? - [x] Short form for “else if” - [ ] A method for string processing - [ ] A logical operator - [ ] An alternative type of loop ### Why is `elif` useful? - [x] It makes the code shorter and clearer - [ ] It replaces the `while` loop - [ ] It disables `else` - [ ] It forces Python to restart ### What does `isinstance(x, int)` do? - [x] Checks whether `x` is of type `int` - [ ] Converts `x` to an `int` - [ ] Deletes the variable `x` - [ ] Creates a new instance ### Why is type checking in Python useful? - [x] To avoid errors, e.g., in division by 0 - [ ] Because Python is strongly typed - [ ] To copy variables into functions - [ ] To end loops ### Sort the following lines to obtain a correctly indented `if-else` statement. Given are: ```python a = 3 b = 5 ``` 1. `if a < b:` 2. ` print("a is smaller than b")` 3. `else:` 4. ` print("a is greater than or equal to b")` ### Sort the lines correctly for a function with `if` and `return`: 1. ` if x > 0:` 2. ` return "positive"` 3. ` else:` 4. ` return "not positive"` ### Put the `if-elif-else` structure in the correct order: 1. `if note == 1:` 2. ` print("very good")` 3. `elif note >= 4:` 4. ` print("passed")` 5. `else:` 6. ` print("not passed")` ### What error is in the following code? ```python if x > 5 print("x is greater than 5") ``` - [x] The colon `:` after the condition is missing - [ ] The indentation is too large - [ ] `if` may not be combined with `>` - [ ] `print` may not be inside an `if` statement ### What is wrong with this code? ```python if y == 10: print("y is equal to 10") ``` - [x] The line with `print` is not indented - [ ] The equality operator `==` is invalid - [ ] The `if` condition must not contain a `:` - [ ] Strings must be enclosed in quotes ### Where is the error in this code? ```python if x > 5: return True else return False ``` - [x] After `else` a colon `:` is missing - [ ] `return` may not be inside an `if` - [ ] Functions may not contain an `if` - [ ] The parentheses around `test` are wrong ### What error is contained in this example? ```python if x = 5: print("x is 5") ``` - [x] An assignment (`=`) is used instead of a comparison (`==`) - [ ] `x` is not a valid variable name - [ ] The `print` statement is deprecated - [ ] Numbers may not be compared