Introduction VII - Introduction to Python III

Before we get started…

In the last Block we’ll learning how to connect separate lines of Python code, i.e. controling the flow of a Python program using Loops and conditional statements. We’ll further learn how and why to write functions in Python.

Make sure to have everything installed to follow along or use the interactive mode.

Note on interactive Mode

As this website is build on Jupyter Notebooks you can click also on the small rocket at the top of this website, select Live code (and wait a bit) and this site will become interactive.

Launch MyBinder

Following you can try to run the code cells below, by clicking on the “run” button, that appears beneath them.

Some functionality of this notebooks can’t be demonstrated using the live code implementation you can therefore either download the course content or open this notebook via Binder, i.e. by clicking on the rocket and select Binder. This will open an online Jupyter-lab session where you can find this notebook by follow the folder strcuture that will be opened on the right hand side via lecture -> content and click on intro_jupyter.ipynb.

If you’ve followed the Setup and installed conda and jupyter, you can simply open a notebook yourself by either:

A. Opening the Anaconda application and selecting the Jupyter Notebooks tile

B. Or opening a terminal/shell type jupyter notebook and hit enter. If you’re not automatically directed to a webpage copy the URL (https://....) printed in the terminal and paste it in your browser

Recap of the last session

Before we dive into new endeavors, it might be important to briefly recap the things we’ve talked about so far. Specifically, we will do this to evaluate if everyone’s roughly on the same page. Thus, if some of the aspects within the recap are either new or fuzzy to you, please have a quick look at the respective part of the first session and second session again and as usual: ask questions wherever something is not clear.

This recap will cover the following topics from the last sessions:

  • variables and types

  • operators and comparison

  • strings, lists and dictionaries

Variables and data types

Exercise 1.1

Two session ago we declared several variables. Could you here create a variable for the following:

  • a variable random_number with a number between 0 and 10

  • a variable called my_favorite_food indicating your favorite food

What data types do each of these variables have?

# Please write your solution here

operators and comparison

Exercise 1.2

Please calculate the following and assign them to the variables a, b, c, etc., if needed:

  • 10 divided by 3

  • 6 to the power of 2

  • 5 times 29 times 48

  • Update c by adding 10

  • Update b by dividing it by 5

  • Is c divisible by 5?

  • Is a larger than b?

  • Are c and b equal?

# Please write your solution here

strings, lists and dictionaries

Exercise 1.3

You just did an experiment asking 10 people for a random number between 0 and 100. What data structure would you use to store these values using python?

Assume the values were: 1; 50; 23; 77; 91; 3; 34; 81; 55; 49. How would you define a variable of your selected data type to store these values?

# Please write your solution here

Exercise 1.4

From the above random numbers you now want to calculate the mean and test if that mean is higher than 50. How would you do that?

# Please write your solution here

Exercise 1.5

Define a variable rand_num_sub_4 that contains the random number from the fourth subject of your experiment and print the statement 'Subject number four picked X as random number', where X shoudle be replaced using string formatting. Assign the variable using it’s index.

Remember: Where do you start to index in Python?

# Please write your solution here

Exercise 1.6

Oh no you made several mistakes! Update your list to correct the mistakes.

  • The random number of the fourth participant wasn’t actually 77 but 76.

  • You forgot to keep track of another participant and actually recorded 11 subjects. You forgot the 8th subject with the number 33. Please add them to your list.

  • You actually don’t want people to choose the number 50. Please remove this value from your list!

# Please write your solution here

Exercise 1.7

Instead of only declaring a list containing all the random numbers, you now want to also assign a personal ID to each subject that you asked. In our case this would simply be Sub_1 to Sub_10 for the first, second, etc. subject, respectively.

Define a dictionary to store this information. Then print all the keys and also redefine the rand_num_sub_4 variable from before, using your new dictionary.

# Please write your solution here

Exercise 1.8

We’ll assign the inscription on the ring from lord of the rings as a string to a variable called lord_of_the_rings. As a reminder, the inscription is:

One Ring to rule them all, One Ring to find them, One Ring to bring them all, and in the darkness bind them.

We also want to create our own fantasy saga, but we are little copy cats. Therefore, we want to define a new statement for the lord of the stones, and only replace the word ring with stone in our sentence. How would you do that?

# Please write your solution here

Exercise 1.9

How long is this statement now compared to before? Further, we want to know what the indices 40 to 45 were in the original inscription and what they would be in our new sentence?

# Please write your solution here

Exercise 1.10

You can split a string into a list of strings using string.split(' '). This function splits the string given the parameter ' ' (i.e the whitespaces) and generates a list. Assign the split list to a new variableand print it.

Split the lord of the stones string into a list of words, sort this new list,

add it to a new variable and print the sorted list. Further print the 10th element of your sorted list.

# Please write your solution here

Thanks for taking the time to go through this recap!

image.png

via GIPHY

If you could solve/answer all questions, you should be well prepared for this session. Otherwise, maybe check out the earlier lessons again or work-out your googling skills!

Indentation & Control Flow

Goals 📍


  • learn about indentation (some form of code structuring)

  • control how our code is executed

    • Conditionals (if - else statements)

    • Iteration (e.g., for-loops, while statements…)

Indentation

Python uses whitespaces to define code blocks. A single whitespaces at the beginning of a line is consider a simple indent. A code block that is indented with the same number of leading whitespaces or tabs should be run together.

In other words: the indentation is part of the syntax in python and one of the major distinctions regarding other programming languages like, e.g. Matlab.

Usually in Python we use four whitespaces for indentation of code blocks (simply hitting the tabkey does the trick). This is defined in the PEP8 Style Guide under whitespace “ethics”.

Let’s see what that means:

days_til_christmas = 51
current_weekday = 'Saturday'

Each set of these statements is called a block, meaning that the lines/variable assignments will be run together. We will see examples of how blocks are important later on.

What happens when we introduce a “wrong” indentation?

days_til_christmas = 51
  current_day = 'Saturday'
  Cell In [2], line 2
    current_day = 'Saturday'
    ^
IndentationError: unexpected indent
    days_til_christmas = 51
        current_day = 'Saturday'
  Cell In [6], line 2
    current_day = 'Saturday'
    ^
IndentationError: unexpected indent

One thing you should remember is that a wrong indentation raises an IndentationError. These can be a lot more impactful than the errors we’ve encountered before, so we won’t adress how to use try - except to catch these here.

Control Flow & structures

Programming languages (i.e. python) allows us to control how code is executed using

  • Conditionals (if - else statements statements)

  • Iteration (e.g., for-loops, while statements…)

Read more on Controlflows here

Conditional statements: if, elif, else

The python syntax for conditional execution of code uses the keywords if, elif (else if), else:




learnbyexample: Python-elif-Statement-Syntax
alphacodingskills: Python-if-elif-else

To quickly demonstrate, we’ll define two False statements and see how if, elif and else deal with booleans:

statement1 = False
statement2 = False

if statement1:
    print("statement1 is True")
    
elif statement2:
    print("statement2 is True")
    
else:
    print("statement1 and statement2 are False")
statement1 and statement2 are False

Note that we’re actually asking if is True. The True boolean is silent in our conditionals.

GIF?

We again encounter that:

Program blocks are defined by their indentation level.

In Python, the extent of a code block is defined by the indentation level (usually a tab or four white spaces). This means that we have to be careful to indent our code correctly, or else we will get syntax errors, as demonstrated above.


https://docs.python.org/3/tutorial/errors.html#syntax-errors](https://docs.python.org/3/tutorial/errors.html#syntax-errors

Examples:

# Good indentation
statement1 = statement2 = True

if statement1:
    if statement2:
        print("both statement1 and statement2 are True")
both statement1 and statement2 are True
# Bad indentation! This would lead to an error
if statement1:
    if statement2:
    print("both statement1 and statement2 are True")  # this line is not properly indented
  Cell In [7], line 4
    print("both statement1 and statement2 are True")  # this line is not properly indented
    ^
IndentationError: expected an indented block

Now what does this error tell us?

statement1 = False 

if statement1:
    print("printed if statement1 is True")
    
    print("still inside the if block")
printed if statement1 is False
still inside the elif block

Oh no! We’re is our output?

Well, let’s try again. This time let’s be explicit in what we’re trying to conditon on by using the is keyword (remember those?)

statement1 = False # still False

if statement1:
    print("printed if statement1 is True")
    
    print("still inside the if block")
    
elif statement1 is False:
    print("printed if statement1 is False")
    
    print("still inside the elif block")
printed if statement1 is False
still inside the elif block

If conditionals care about indentation. What happens if we’re not on the same indentation level?

statement1 = True
statement1 = not False # so True


if statement1:
    print("printed if statement1 is True")
    
print("now outside the if block")
printed if statement1 is True
now outside the if block

Using the or & and logic from the last session, we can even combine statements:

print("Statement2 was:", statement2, "right?")
print('So:')

if statement1 and statement2:
    print("printed if statement1 and statement2 are True")
elif statement1 or statement2:
    print("printed if either statement1 or statement2 is True")
else:
    print("printed if no statement is True")
Statement2 was: True right?
So:
printed if statement1 and statement2 are True

Exercise 2.1

You want to go to the cinema, but you first need to check whether you have enough money. First define a variable indicating the amount of money in your wallet money_in_wallet as 6 EUR and the ticket price for the cinema ticket_price as 10 EUR.

Indicate with a print statement IF you can afford to go to the cinema! Use both if and else conditionals to print wether you’re going out or staying home.

### Write your solution here ###

Exercise 2.2

Different films cost different amounts of money. Use if statements to tests which films you can afford and print those. The films cost:

  • James Bond: 15 EUR

  • Spider Man - No Way Home: 11 EUR

  • Dune: 6 EUR

  • Ghostbusters: 5 EUR

Note: as always in coding, there are several ways to get the right solution.

### Write your solution here ###

Exercise 2.3

It’s your lucky day! You happen to find exactly 34 EUR right in front of your house. You want to celebrate this by inviting as many of your 5 friends as possible to the cinema.

How many friends can you invite, while also paying for yourself? First update your money in your wallet, test this using if statements and print the result.

### Write your solution here ###

Exercise 2.4 - Last one, I promise!

This year you want to treat your dog with a christmas present but obviously only if it was a good boy. Depending on the money in your wallet, you can either buy a new toy for 10 EUR or instead go on a nice long walk if you don’t have any money.

Write a nested if statement to test which present you can buy if your dog was a good boy. Your current endowment is 11 EUR.


### Write your solution here ###

These many if statements were very tedious. Can we not do this more efficiently?


Wait no more. We have a solution (at least for some situations)!

Loops

In python, loops can be programmed in a number of different ways. The most common is the for loop, which is used together with iterable objects, such as lists. The basic syntax is:


https://www.learnbyexample.org/wp-content/uploads/python/Python-for-Loop-Syntax.png
via GIPHY
for i in [1,2,3]:
    print(i)
1
2
3

But why that i? Honestly, that is mostly a mixture of laziness and force of habbit. Technically you could give your variable that you iterated over any name that is not a python keyword or function name and in general you should be as specific as possible, whenever possible for everyone’s sake.


via Reddit

So let’s try that again:

for num_in_list in [1,2,3]:
    print(num_in_list)
1
2
3

Remember that our iterable_variable will be continously updated. So if we call num_in_list again, we’ll get the last assigned value, i.e. the last item in our list.

num_in_list
3

So our for loop iterates over the elements of the supplied list and executes the contained code block once for each element. Any kind of list can be used in the for loop.

It’s common to employ the range() function, which we’ve met in the previous session, to define how many times a loop should repeat. For example:

for i in range(4): # by default range start at 0
    print(i)
0
1
2
3
#  to demonstrate you could also create a list using range by
list_range = list(range(4))
list_range

# results in the same as before
for i in list_range:
    print(i)
0
1
2
3
print(i)
3

Note: range(4) does not include 4! Try to remember the aspects of indexing and slicing we addressed during the session on strings and lists.

for x in range(-3,3):
    print(x)
-3
-2
-1
0
1
2

Again, you can use any kind of list, so let’s try a list of strings:

for word in ["scientific", "computing", "with", "python"]:
    print(word)
scientific
computing
with
python

and the variable word again contains the last item of the list we used, i.e. the variable assumes the value of the last loop iteration

word
'python'

To iterate over key-value pairs of a dictionary we need to change up our syntax a bit. So we include 2 variables when defining our for loop, like so:

# define a dictionary
params = {
    'parameter1': 'A',
    'parameter2': 'B',
    'parameter3': 'C',
    'parameter4': 'D'
}
print(params)
{'parameter1': 'A', 'parameter2': 'B', 'parameter3': 'C', 'parameter4': 'D'}
list(params.items())
[('parameter1', 'A'),
 ('parameter2', 'B'),
 ('parameter3', 'C'),
 ('parameter4', 'D')]
# call the for loop with a variable provided for keys and values
for key in params.items():
    print(key)
('parameter1', 'A')
('parameter2', 'B')
('parameter3', 'C')
('parameter4', 'D')
# call the for loop with a variable provided for keys and values
for key in params.keys():
    print(key + " = ")
parameter1 = 
parameter2 = 
parameter3 = 
parameter4 = 
# or what you'll usually see
for i, j in params.items():
    print(i + " = " + str(j))
parameter1 = A
parameter2 = B
parameter3 = C
parameter4 = D

Sometimes it is useful to have access to the indices of the values when iterating over a list. We can use the enumerate function for this:

# note that the convention is to call our index varivale `idx`
for idx, x in enumerate(range(-3,3)):
    print('loop number = ' + str(idx))
    print(x)
loop number = 0
-3
loop number = 1
-2
loop number = 2
-1
loop number = 3
0
loop number = 4
1
loop number = 5
2

break, continue and pass

But at times it is necessary to further control how a loop behaves. We’ll therefore introduce a few control statements

To control the flow of a certain loop we’ll use break, continue and pass.


To illustrate this let’s first create another list

rangelist = list(range(10))
print(list(rangelist))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Now if we want our loop to stop running once a condition is met we need our old friends the if and else conditional statements. We’ll evaluate if a certain number is contained in our list and break the loop once a [4, 5, 7, 9] is found.

for number in rangelist:
    # Check if number is one of
    # the numbers in the list.
    if number in [4, 5, 7, 9]:
        # "Break" terminates a for loop without
        # executing the "else" clause.
        print(number)
        print('number ' + str(number) + ' encountered. Loop breaks here!')
        break
    else:
        # "Continue" starts the next iteration
        # of the loop. It's rather useless here,
        # as it's the last statement of the loop.
        print(number)
        print('continued')
        continue
else:
    # The "else" clause is optional and is
    # executed only if the loop didn't "break".
    pass # Do nothing
0
continued
1
continued
2
continued
3
continued
4
number 4 encountered. Loop breaks here!

Let’s see what continue and pass can actually do. Continue implies that we don’t want our for loop to end, but rather to skip the rest of code block the for the current iteration. So:

for number in rangelist:
    if number in [4, 5, 6]:
        print('number ' + str(number) + " encountered. We're supposed to break, right?")
        print("oh no the loop can't hear us it has a continue statment in it!")
        continue  # stop here, continue with next loop

        print('so this break statement will never be read?!')
        break

    elif number in [8, 9]:
        print(str(number))
        print("break? break!")
        break
        
        print('so this continue statement will never be reached?!')
        continue        

    else:
        print('else cond = ' + str(number))
else cond = 0
else cond = 1
else cond = 2
else cond = 3
number 4 encountered. We're supposed to break, right?
oh no the loop can't hear us it has a continue statment in it!
number 5 encountered. We're supposed to break, right?
oh no the loop can't hear us it has a continue statment in it!
number 6 encountered. We're supposed to break, right?
oh no the loop can't hear us it has a continue statment in it!
else cond = 7
8
break? break!

What about pass then? We’ll use it to tell our loop to do nothing if a certain condition is met. Exciting, i know!

for number in rangelist:
    if number in [4, 5, 7, 9]:
        print('If statements: number ' + str(number) + ' encountered!')
    elif number in [2, 3, 6]:
        pass
        print('elif_statement: number ' + str(number) + ' encountered!' + ' ...continuing...')
        pass
    else:
        print('else_statement: number ' + str(number) + ' encountered!' + ' ...continuing...')
        continue
else_statement: number 0 encountered! ...continuing...
else_statement: number 1 encountered! ...continuing...
elif_statement: number 2 encountered! ...continuing...
elif_statement: number 3 encountered! ...continuing...
If statements: number 4 encountered!
If statements: number 5 encountered!
elif_statement: number 6 encountered! ...continuing...
If statements: number 7 encountered!
else_statement: number 8 encountered! ...continuing...
If statements: number 9 encountered!

This is mostly used as a placeholder for code you haven’t written yet and allows us to avoid syntax errors

for number in rangelist:
    if number in [4, 5, 7, 9]:
        print('If statements: number ' + str(number) + ' encountered!')
    elif number in [2, 3, 6]:
        # indented code-block expected, this will lead to an syntax error

    else:
        print('else_statement: number ' + str(number) + ' encountered!' + ' ...continuing...')
        continue
  Cell In [9], line 7
    else:
    ^
IndentationError: expected an indented block

Exercise 3.1

Use a for loop to print every even number from 0 to 10

Hint: you can check if a number is even with number % 2 == 0. Remember the modulo operator?

### Write your solution here ###

Exercise 3.2

Use a for loop that iterates over all days in december (31) and always prints the number of days left until christmas. This loop should break once christmas is reached and should wish you a merry christmas using a print statement.

Hint: define a variable called christmasday that is assigned before your loop starts

### Write your solution here ###

Exercise 3.3

Create a list of your three most favourite foods and iterate over it. In each iteration print the position (aka index) and the food. For example:

    1. Pasta

    1. Pizzas

    1. Wraps

Hint: all necessary commands can be found in this script

### Write your solution here ###

List comprehensions: Creating lists using for loops:

Now you might encounter some strange looking loops when looking for code online. To keep code sparse and simple looking programmers may sometimes opt to use a single line instead of an indented block for their loops by using list comprehensions:

A convenient and compact way to initialize lists would then look like this:


https://www.learnbyexample.org/wp-content/uploads/python/Python-List-Comprehension-Syntax.png

How would you explains this code snippet in natural language?
https://4.bp.blogspot.com/-uRPZqKbIGwQ/XRtgWhC6qqI/AAAAAAAAH0w/--oGnwKsnpo00GwQgH2gV3RPwHwK8uONgCLcBGAs/s1600/comprehension.PNG

We’ll use it to square every number x in a list containing the numbers 0 - 4 and add those to a new list called list_x_squared:

list_x_squared = [x**2 for x in range(0,5)]

print(list_x_squared)
[0, 1, 4, 9, 16]

You can also use an if statement in your list comprehension. But be careful with the ordering. A single if can be after the for loop. However, if and else together have to be in front. Lets see some examples.

# if our x is greater than 2, run the list comprehension
list_2 = [x**2 for x in range(0,5) if x > 2]
print(list_2)
[9, 16]
list_3 = [x**2 for x in range(0,5) if x > 2 else x]
print(list_3)
  Cell In [31], line 1
    list_3 = [x**2 for x in range(0,5) if x > 2 else x]
                                                ^
SyntaxError: invalid syntax

Now lets put the if-else statement before the for loop

list_3 = [x**2 if x > 2 else x for x in range(0,5)]
print(list_3)
[0, 1, 2, 9, 16]

Don’t be discouraged if that looks confusing, you’ll get the hang of it once you actually start working or fiddling with list comprehensions. Further, it is always fine to use the explicit loop structure, we’ve learned before!

Exercise 3.4

Use a list comprehension to create a list containing all letters that are in the word ‘human’.

As always, there are different ways to solve this

### Write your solution here ###

Exercise 3.5

Use a list comprehension to create a list with all even numbers from 0 to 19.

Where would you use your if statement?

Exercise 2.6

Use a list comprehension to create a new list with all uneven numbers from 0 to 19 and set all even numbers to 0.

Hint: Probably a good idea to use both if and else. In the End the list should still have a length of 20.

### Write your solution here ###

while loops:

Sometimes we do not have a certain iterable that we want or can use for our loops. Enter the while loop.

A while loop is used when you want to perform a task indefinitely, until a particular condition is met. It is s a condition-controlled loop.

https://www.learnbyexample.org/wp-content/uploads/python/Python-while-Loop-Syntax.png
https://media.geeksforgeeks.org/wp-content/uploads/20191101170515/while-loop.jpg

If we for example want to print all number below 5, we simply do:

i = 0

while i < 5:
    print(i)
    
    i = i + 1
    
print("done")
2
3
4
done

Note that the print "done" statement is not part of the while loop body because of the difference in the indentation.

But what is the i = i + 1 code block? Check back in the first python lesson for a qucik refresher, if necessary.

For our next trick, we’ll be a bit more explicit in our variable naming:

counter = 0

while counter < 5:
    print(counter)
    
    counter = counter + 1
    
print("done")
0
1
2
3
4
done

So the i = i + 1 updates our conditional variable on each iteration by 1 + it’s previous value.

If you would forget the i = i+1 expression and our conditional variable would never be updated, we’d be stuck in our while loop forever, as the condition is never set to not True . Therefore whileloops can be dangerous to use, if you’re not careful.

# if you run this cell you will need to stop the kernel, as it is an infinite loop
# i = 0
# while i < 5:
#     print(i)

You can also include an else statement after your while loop, to include a codeblock that should be executed once the condition is False

counter = 0

while counter < 3:
    print("Inside loop")
    counter = counter + 1
else:
    print("Inside else")
Inside loop
Inside loop
Inside loop
Inside else

Exercise 3.7

Calculate the number of friends that you can invite to the cinema using a while-loop.

Remember:

  • money = 43

  • ticket_price = 10

Hint: You may want to alter your money to calculate this, by updating the “money” variable as demonstrated above.

### Write your solution here ###

Functions

Careful rule of thumb for great programming: “Whenever you copy-paste while coding, you do something wrong.’

We use functions to solve problems that are repetitive.

What should you put into a function:

  • Anything, that you will do more than once

  • All code blocks that have some kind of meaning, e.g. calculating the square root of a value

  • Code that can be increased in readability, where you would like to add a comment

A function

  • is a block of code that only runs when explicitly called

  • can accept arguments (or parameters) that alter its behavior

  • can accept any number/type of inputs, but always return a single object

Note: functions can return tuples (may look like multiple objects)

A function in Python is defined using the keyword def, followed by a function name, a signature within parentheses (), and a colon :. The following code, with one additional level of indentation, is the function body. The return statement at the end of the function defines what the ouput will be, when the function is called.




Somewhat intimidating, right? Let's check the different components in further detail.
def say_hello():
    # block belonging to the function
    print('hello world')

say_hello() # call the function
hello world

The function above does not take any arguments but only executes the code within. As there is no explicit return statement the function will simply return None.

Let’s check that. To access the return value of a function, you simply assign the function to a variable, as we learned above and in the previous sessions.

greetings = say_hello()
hello world
print(greetings)
None

As you see, the function still printed the 'hello world' but the returned value that was stored in greetings is None.

A function can also accept arguments within the parentheses, making it super flexible. For example:

def calc_power(x, p):
    # x is our base
    # p is our exponent
    power = x ** p
    return power
25
27

Now we can input specific terms or variables into our function, like so:

print(calc_power(5,2))
print(calc_power(3,3))
25
27

So the input arguments take the place of the placeholder variables we x and p we used when we initially defined our funtion. What happens if our input arguments are of a different variable type then expected in the function body?

print(calc_power(5,'string'))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [9], line 1
----> 1 print(calc_power(5,'string'))

Cell In [5], line 4, in calc_power(x, p)
      1 def calc_power(x, p):
      2     # x is our base
      3     # p is our exponent
----> 4     power = x ** p
      5     return power

TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'str'

Here we get a type error that explicitly references the function (compare the error output to the above defined function) and tells us that a string can’t be used for the mathematical operation we’re trying to use in line 4 of our function.

What happens if we’re missing an input argument?

print(calc_power(5))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [8], line 1
----> 1 print(calc_power(5))

TypeError: calc_power() missing 1 required positional argument: 'p'

Again, a rather explicit error. Our function was defined as requiring 2 input arguments, so we need to provide exactly that number.

As we used a return statement this time, the power that we calculated is returned and can be printed or assigned to a variable.

five_to_pow_2 = calc_power(5,2)
print(five_to_pow_2)
25

These were all simple examples, however, we can also put more complex code blocks into a function.

Let’s define a function that tells us if a certain input argument is larger than another input argument or if they are equal.

def get_maximum(a, b):
    maximum = None
    if a > b:
        print( a, 'is maximum')
        maximum = a
    elif a == b:
        print(a, 'is equal to', b)
        maximum = a
    else:
        print(b, 'is maximum')
        maximum = b
    return maximum
4 is maximum
7 is equal to 7

Now let’s call that function using the input arguments 3 and 4

# directly pass literal values
maximum = get_maximum(3, 4)
4 is maximum

and that value is now stored in our variable called maximum for future use

maximum
4

We can also use variables as input arguments, which is useful in larger code bodies

x = 7
y = 7

# pass variables as arguments
maximum = get_maximum(x, y)
7 is equal to 7
maximum
7

We can also return multiple values from a function using tuples:

def calc_powers(x):
    """
    Return a few powers of x.
    """
    return x ** 2, x ** 3, x ** 4
x = 5
powers = calc_powers(x)
print(powers)
(25, 125, 625)

As you see, your output variable powers is a tuple that contains the output values of the function. We can however, split this tuple directly into the specific values. This can help to make your code more readable. Make sure to speficy the same number of variables to assign to as values your return statements provide. Makes sense?

x2, x3, x4 = calc_powers(5)
print(x3)
125

Very important:

Variables inside a function are treated as local variables and therefore don’t interfere with variables outside the scope of the function. This further means that a variable that is defined in a function is not defined outside of the function.

To demonstrate let’s first define a function using the loca variable x:

def func(x):
    print('x is', x)
    x = 2
    print('Changed local x to', x)

Now we’ll define a variable called x as we would usually do it by assigning a value to x

x = 50
print(x)
50

Now we call our function, which takes the x that we’ve just defined outside of the function as an input

func(x)
x is 50
Changed local x to 2

Via the to print statements in the function we see that indeed takes our x as input and prints it’s previously assigned value, i.e. 50. Now in the function body the value of the local x variable is changed to x = 2.

Let’s see what that means for our initial x variable

print(x)
50

So the function-internal x variable and the externally defined x variable are to different instances.

To change our so called global variable, we could implement a return statement as shown above and assign the function output to the x variable

Another way to get the same result is to access a local function variable, by extending it’s "local scope" with the keyword global.

x = 50

def func():
    global x

    print('x is', x)
    x = 2
    print('Changed global x to', x)

func()
print('Value of x is', x)
x is 50
Changed global x to 2
Value of x is 2

It’s generally best to avoid such ambiguity, but sometimes it’s necessary to access the global variable inside of functions.

Optionally, but highly recommended, we can define a so called "docstring", which is a description of the functions purpose and behavior. The docstring should follow directly after the function definition, before the code in the function body.

You can also define the input and return parameters in the docstring. There are several conventions to do this. You can find them here.

def func_with_docstring(s):
    """
    Print a string 's' and tell how many characters it has    
    :param s (string): input string of any length
    :returns: None
    """
    
    print(s + " has " + str(len(s)) + " characters")

Which now allows us to use the help function, to understand what the function is supposed to be doing

help(func_with_docstring)
Help on function func_with_docstring in module __main__:

func_with_docstring(s)
    Print a string 's' and tell how many characters it has    
    :param s (string): input string of any length
    :returns: None
func_with_docstring('So much fun to write functions')
So much fun to write functions has 30 characters

Positional vs. keyword arguments

  • Positional arguments are defined by position and must be passed

    • Arguments in the function signature are filled in order

  • Keyword arguments have a default value

    • Arguments can be passed in arbitrary order (after any positional arguments)

You might not think that at the moment, but coding is all about readability. You only write it once, but you will probably read it several times.

In Python we can increase readability when calling a function by also naming our positional arguments. For example:

def calc_power(x, power):
    return x ** power

We could now simply input the variables that we want to compute:

calc_power(2, 2)
4

The input in the function is positionally defined. Hence, the first parameter represents x and the second power. But does this really tell you what is happening, just from reading the function? We can increase readability by calling the parameters by their names:

calc_power(x=2, power=2)
4

Now everyone that looks at your code, can directly see what is happening. If we explicitly list the name of the arguments in the function calls, they do not need to come in the same order as in the function definition. This is called keyword arguments and is often very useful in functions that take a lot of optional arguments.

Additionally we could also give default values to the arguments the function takes:

def calc_power(x, power=2):
    return x ** power

If we don’t provide a value for the power argument when calling the the function calc_power it defaults to the value provided in the function definition:

x = 3
calc_power(x)
9

Such default values are especially useful for functions that take a lot of arguments, as it reduces the amount of arguments that you have to pass to the function.

Arguments with a default value

  • have to be defined /called AFTER the positional values

  • don’t have to be called in order

Let’s quickly talk about function names…

Function names are very important. They will allow you to tell the story of your code. Time spent on naming is never wasted time in coding.

The ‘pythonic’ way to write functions is to also imply in the name what the function will do.

Some examples:

  • when you calculate something you can use calc in your function name.

  • when you want to test if something is true with your function you could use is. E.g., is_above_value(x, value=10) could return True if the input value x is above the default value.

  • use print if your function only prints statements

Argument unpacking with *args and *kwargs

Sometimes functions need to accept arbitrary/unknown arguments..

  • Python handles this elegantly via the *args and **kwargs conventions

    • *args: a list of all unassigned positional arguments

    • **kwargs: a dictionary of all unassigned keyword arguments

Note: the names args and kwargs are purely conventional. The important thing are to use a single * and some variable name to indicate args and two, i.e. ** to inidcate kwargs

Tal yarkoni’s intro to python

So next we’ll try to understand what that actually means by playing around with a new function called print_args_and_kwargs()

# https://github.com/neurohackademy/introduction-to-python/blob/master/introduction-to-python.ipynb

def print_args_and_kwargs(*args, **kwargs):
    print("Args:", args)
    print("Kwargs:", kwargs)

The great thing is that *args allows us to pass a varying number of positional arguments, instead of us being limited to the amount of arguments we specified when we defined functions before.

Let’s try that:

print_args_and_kwargs([1,2,3],[3,4,5], '17', 'float')
Args: ([1, 2, 3], [3, 4, 5], '17', 'float')
Kwargs: {}

**kwargs does much the same, except it allows us to pass any number of keyword arguments to a function, so:

print_args_and_kwargs(name='michael', number='017xxx', adress='5G.103', e_mail='ernst@psych')
Args: ()
Kwargs: {'name': 'michael', 'number': '017xxx', 'adress': '5G.103', 'e_mail': 'ernst@psych'}

notice that **kwargs procudes a key - value pair for each input parameter

We can now use this in combination with return to assign the input arguments transformed in structure to a variable:

def return_args(*args):
    print("Args:", args)
    return args


args = return_args([1,2,3], 7, 'str')
type(args)
Args: ([1, 2, 3], 7, 'str')
tuple

The *args function provides a tuple, what do you believe a **kwargs may produce?

def return_kwargs(**kwargs):
    print("kwargs:", kwargs)
    return kwargs
kwargs = return_kwargs(name='michael', number='017xxx', adress='5G.103', e_mail='ernst@psych')
print(kwargs)
print(type(kwargs))
kwargs: {'name': 'michael', 'number': '017xxx', 'adress': '5G.103', 'e_mail': 'ernst@psych'}
{'name': 'michael', 'number': '017xxx', 'adress': '5G.103', 'e_mail': 'ernst@psych'}
<class 'dict'>

For a more in-depth explanation and examples check out the tutorial by geeks for geeks

Exercise 4.1

Define a function called get_longest_string that takes two strings as input and returns the longer string. If the strings have the same length, the first one should be returned.

Call your function once, using positional arguments and once using keyword arguments.

## Write your solution here

Exercise 4.2

Define a function named happy_holidays that wishes the user happy holidays.

It takes the name of the user as input argument.

When the user does not define a name, your function should use a default value.

Then call it once, inputting a user name and once without.

## Write your solution here
And this concludes our introduction sessions.

Great work everyone! You’re on your way to actually understand this madness!


via GIPHY

Achknowledgments


Michael Ernst
Phd student - Fiebach Lab, Neurocognitive Psychology at Goethe-University Frankfurt

Maren Wehrheim

Phd student - FIAS(Frankfurt Institute for Advanced Studies) - Kaschube-Lab and Fiebach Lab, Neurocognitive Psychology at Goethe-University Frankfurt logo

@maren7794

Peer Herholz (he/him)
Research affiliate - NeuroDataScience lab at MNI/MIT
Member - BIDS, ReproNim, Brainhack, Neuromod, OHBM SEA-SIG, UNIQUE

logo logo   @peerherholz