Functions

We have already used a ton of functions; print(), list.append(), input(), int(), etc. This module will focus on learning how to write your own functions. Now there are a ton of use cases for functions, but typically there are 3 reasons to create a function vs just writing the code inline:

  1. Reuse; if your code can be generalized and reused in many places in your software, then usually it's better as a function. i.e. if you're creating an e-commerce shop having a function to buy something is better than writing every possible purchase that could be made inline.

  2. Organization; Even if code is being used once it's sometimes easier to read if you are just calling functions instead of inline code. Let's say for example you are creating a game, if you create functions with good names it's often easy to tell what's happening without having to go through many lines of code:

# Gameloop
while turns < 100:          # Game goes until 100 score
    player_move(player_one) # Player one's move
    player_move(player_two) # Player two's move
    turns += 1

if player_one.score > player_two.score:
    print("Player Two Wins!")
elif player_one.score < player_two.score:
    print("Player Two Wins!")

Even without knowing what the functions are exactly doing you can get an idea of what's happening here. There's some game that goes on until the turns variable is 100 and player's take turn making moves, then after 100 turns whoever has higher score wins

  1. Modules/API's; Sometimes your code won't always be directly seen by people. Python uses a lot of modules (which can also be called, libraries, API's, or packages) to get a lot of functionality. For example earlier in one of the challenges we imported the function random.randint() to generate a random number. If the creator of the module wrote the code inline then there would be no way to get that simple functionality out of the module (there will be more detail about this in the next module).

Basic Syntax

The basic syntax to define a function is using the keyword def followed by the function name (what you want to call it), then the arguments (what data the function needs to run), then the docstring (description of the function), then the function body (the code the function needs to run), followed by an optional return statement (data the function should give back). Here is what it should look like if you have a function called sum that takes two integers, adds them, and returns the result:

def sum(num_1, num_2):
    """
    Takes two variables (int's or floats), and
    adds them together, then returns the result
    """
    result = num_1 + num_2
    return result

Commenting/Docstrings

Remember earlier in the course when I said that commenting is important? Well trust me it's really important for functions. Properly commenting functions can save you (and others who use your code) a ton of time. Specifically functions have special types of comments called docstrings, these docstrings are ways to tell people how to use your function properly.

Most text editors/IDE's use docstrings to help people write code with functions. For example here is what the docstring for the random.randint() function looks like in visual studio code:

docstrings

As you can see it tells you what arguments you need (a and b), what types they should be (both ints), tells you what you will get back/returned (an int), and gives a short description of what the function does. Now this is sort of a best case scenario, but at least you should include a short description of what the function does, and what it returns even if you don't bother with anything else.

To create docstrings you simply need to add a multiline comment as the first line of a function definition. There's a ton of debate in python as to what is the best way to write docstrings, personally I prefer the numpy style but there are a few other options if you don't (click here to see a post I made about this). A basic docstring for the sum() example above would look like this:

def sum(num_1, num_2):
    """
    Takes two variables and sums them.

    Parameters
    ----------
    num_1 : (int)
        The first operand of the addition.

    num_2 : (int)
        The Second operand of the addition.

    Returns
    -------
    int: 
        The sum of the two numbers
    """
    result = num_1 + num_2
    return result

Which then gives you something like this in visual studio code when you try to call the function:

sum_docstring

Variable Scope

Variable Scope just refers to where you can access a variable from. In the case of functions you can only access variables that are in the functions from inside the function. For example:

greeting = "Hello You!" # This would be called a global variable because it's not inside a function

def greet():
    """Prints a greeting"""
    greeting = "Good Bye!" # Now a 'function/local' greeting variable exists
    print(greeting) # This will use the 'function/local' variable 

greet() # Prints: Good Bye!
print(greeting) # Prints: Hello You!

As you can see the variable greeting that's outside the function has a lower 'priority' than the one inside the function, and the one inside the function has no effect on the one outside the function. As a rule of thumb you should always aim for 'function/local' variables since only the function has access to them.

Arguments/Parameters

Arguments (sometimes called parameters), are pieces of information you use to complete whatever your function needs to do. In the example I gave of the sum() function at the beginning of this module, there were two arguments: num_1 and num_2.

Arguments are passed to functions and create a 'function/local' variable of the same name, to be used in any code running inside the function. They can be of any type, but there are a few different ways to setup arguments:

Positional Arguments

These are the most common arguments, this is exactly what I used in sum() from earlier. They are arguments that the values are given based on where they were inputed. For example let's take a function called difference which subtracts two numbers:

def difference(num_1, num_2):
    """
    Takes two variables and subtracts them them.

    Parameters
    ----------
    num_1 : (int)
        The first operand of the subtraction.

    num_2 : (int)
        The Second operand of the subtraction.

    Returns
    -------
    int: 
        The difference of the two numbers
    """
    result = num_1 - num_2
    return result

Depending on which order I pass my arguments to the function changes which value is assigned to num_1 and num_2:

difference(4, 2) # Returns 2 because num_1 = 4 and num_2 = 2
difference(2, 4) # Returns -2 because num_1 = 2 and num_2 = 4 now

Keyword Arguments

These are less common but incredibly useful ways of declaring variables that have 2 distinctions compared to positional arguments:

  1. They can be in any order
  2. They have a default value provided

Let's take the example of the greet() function from earlier and give people the ability to customize the message:

def greet(name="John doe", greeting="Hello there: "):
    """Greets a person with the greeting and their name

    Parameters
    ----------
    name: (str)
        The name to greet by.
    greeting: (str)
        The greeting to greet by.
    """
    print(name, greeting)

Now there are a few ways to call this function, because there are defaults for both variables already set we could just call it as it is:

greet() # Prints: Hello there: John Doe

or we can just change 1 of the variables:

greet(name = "Kieran Wood") # Prints: Hello there: Kieran Wood
greet(greeting = "How it be: ") # Prints: How it be: John Doe

or both in any order:

greet(name = "Kieran Wood", greeting = "How it be: ") # Prints: How it be: Kieran Wood
greet(greeting = "How it be: ", name = "Kieran Wood") # Prints: How it be: Kieran Wood

Function Body

As we have seen the function body is simply the statements that make up what the function needs to do. A function body for example could be a calculation, running the code necessary to print something to the screen, or even creating and returning information (such as in random.randint()).

Returns

Return statements are optional, by default if one is not defined the function returns what's called a None (basically nothing, it just let's python know it's done running). You can return any type; an int, float, a collection (such as a string, list, dictionary), a logical or arithmetic operation, or even return a call to another function.

Type declarations

Optionally you can give what are called type declarations, these allow you to specify what type arguments and/or returns should be. This can be helpful to let people know what the function is expecting to receive and return. It can also help cut down on the amount you need to write for docstrings. Let's take the example of the sum() function from earlier:

def sum(num_1: int, num_2: int) -> int:
    """
    Takes two variables and sums them.

    Parameters
    ----------
    num_1 :
        The first operand of the addition.

    num_2 :
        The Second operand of the addition.

    Returns
    -------
    The sum of the two numbers
    """
    result = num_1 + num_2
    return result

As you can see all you need to do is add a : type after an argument, and an -> return_type after the arguments to specify argument types and return types respectively. One thing to keep in mind is that this DOES NOT ENFORCE THE TYPES, it merely gives people an idea of what they should do not what they have to do.

Exercises

""" =========== Exercise 1 ============= Add a docstring to the function below. The function takes a list and prints all the items in the list. Bonus: Try using type declaration for the argument. """ def print_list(list_to_parse): """TODO: Add docstring""" for item in list_to_parse: print(item) """ =========== Exercise 2 ============= Implement the delete_item() function below, all the details to do so should be there for you. Hint: you can use del(list[index]) to remove an item from a list. """ def delete_item(list_to_parse, item_index): """Takes a list, removes an item at the specified index and returns the list Parameters ---------- list_to_parse: The list to remove the item from item_index: The index to remove an item from Returns ------- The list with the item removed """ # Do stuff here pass # This just tells python to do nothing; remove it when you add your code shopping_list = ["eggs", "ham", "sausages"] # A test list to remove an item from shopping_list = delete_item(shopping_list, 1) # Should remove 'ham' from the list print(shopping_list)

Challenges

""" =========== Challenge 1 ============= create a function called validate_input(), that takes a string, and returns True if the following conditions are met, and False if they aren't: 1. The input is all lowercase (use variable.islower()) 2. It doesn't end with 'yeet' 3. There are no q's in the input. Hint: if you use a for loop you can see each letter of a string, or the in operator to check for a value in a string. """ user_input = "this is valid" user_input_1 = "This has a capital" user_input_2 = "this has a yeet" user_input_3 = "this has a q" def validate_input(user_input: str) -> bool: """TODO: Docstring, and function logic""" pass # Remove this after implementing the function print(validate_input(user_input)) # Should be True print(validate_input(user_input_1)) # Should be False print(validate_input(user_input_2)) # Should be False print(validate_input(user_input_3)) # Should be False """ =========== Challenge 2 ============= Create two functions (with docstrings): 1. add_to_dictionary(): that takes a dictionary, a key, and a value as arguments, then returns the dictionary with the key and value added. 2. print_dictionary(): A function that takes a dictionary as an argument and prints it's keys and values. Hint: If you loop over a dictionary with a for loop each iteration is a key. Extra Hint: Keys are how you access values in a dictionary. """ def add_to_dictionary(dictionary: dict, key: str, value) -> dict: """TODO: Docstring, and function logic""" # Your code goes here return dictionary def print_dictionary(dictionary: dict): """TODO: Docstring, and function logic""" # Your code goes here user = { "name": "Kieran Wood", "age" : 21 } user = add_to_dictionary(user, "country", "Canada") print_dictionary(user)

Solutions




""" =========== Exercise 1 ============= Add a docstring to the function below. The function takes a list and prints all the items in the list. Bonus: Try using type declaration for the argument. """ def print_list(list_to_parse: list): """Takes in a list and print's it's items Parameters ---------- list_to_parse: The list you want to print the items of.""" for item in list_to_parse: print(item) """ =========== Exercise 2 ============= Implement the delete_item() function below, all the details to do so should be there for you. Hint: you can use del(list[index]) to remove an item from a list. """ def delete_item(list_to_parse, item_index): """Takes a list, removes an item at the specified index and returns the list Parameters ---------- list_to_parse: The list to remove the item from item_index: The index to remove an item from Returns ------- The list with the item removed """ del(list_to_parse[item_index]) # Remove the item return list_to_parse shopping_list = ["eggs", "ham", "sausages"] # A test list to remove an item from shopping_list = delete_item(shopping_list, 1) # Should remove 'ham' from the list print(shopping_list) """ =========== Challenge 1 ============= create a function called validate_input(), that takes a string, and returns True if the following conditions are met, and False if they aren't: 1. The input is all lowercase (use variable.islower()) 2. It doesn't end with 'yeet' 3. There are no q's in the input. Hint: if you use a for loop you can see each letter of a string, or the in operator to check for a value in a string. """ user_input = "this is valid" user_input_1 = "This has a capital" user_input_2 = "this has a yeet" user_input_3 = "this has a q" def validate_input(user_input: str) -> bool: """ Takes a string and validates it against three criteria: 1. The input is all lowercase (use variable.islower()) 2. It doesn't end with 'yeet' 3. There are no q's in the input. Parameters ---------- user_input: The value to validate. Returns ------- True if none of the criteria are met and False if any are. """ if not user_input.islower(): return False if user_input.endswith("yeet"): return False if "q" or "Q" in user_input: # Check if q is a letter return False return True # If none of the conditions above are met print(validate_input(user_input)) # Should be True print(validate_input(user_input_1)) # Should be False print(validate_input(user_input_2)) # Should be False print(validate_input(user_input_3)) # Should be False """ =========== Challenge 2 ============= Create two functions (with docstrings): 1. add_to_dictionary(): that takes a dictionary, a key, and a value as arguments, then returns the dictionary with the key and value added. 2. print_dictionary(): A function that takes a dictionary as an argument and prints it's keys and values. Hint: If you loop over a dictionary with a for loop each iteration is a key. Extra Hint: Keys are how you access values in a dictionary. """ def add_to_dictionary(dictionary: dict, key: str, value) -> dict: """ Adds a value to a dictionary with a given key Parameters ---------- dictionary: The dictionary to add the key-value pair to. key: The key to be added for the value to be added. Value: The value to be added for the key to be added. Returns ------- The dictionary with the key-value added. """ dictionary[key] = value return dictionary def print_dictionary(dictionary: dict): """ Prints the keys and values of a dictionary. Parameters ---------- dictionary: The dictionary to print. """ for key in dictionary: print(key, dictionary[key]) user = { "name": "Kieran Wood", "age" : 21 } user = add_to_dictionary(user, "country", "Canada") print_dictionary(user)