Day 5: Functions

Day 5: Functions

Review of Control Structures in Python

Control structures are the backbone of any programming language, Python is no exception. They dictate the flow of control in a program. In Python, we primarily use three types of control structures – sequential, selection (or decision making), and iteration (or looping).

Sequential Control Structure

Sequential control is the default control structure where statements are executed line by line, in the order they appear in the script. Here’s a simple example:

print("This is the first line.")
print("This is the second line.")
print("This is the third line.")

In this case, Python executes the print statements one after another in sequence.

Selection Control Structure

In Python, we use if, elif (else if), and else statements for decision-making purposes. The selection structure tests a condition, and then runs one sequence of code if the condition is true, and another if the condition is false.

Here’s a simple example:

temperature = 20

if temperature < 0:
    print("It's freezing!")
elif 0 <= temperature < 20:
    print("It's cold!")
else:
    print("It's warm!")

In this code snippet, Python checks the if condition first. If the condition fails, it checks the elif condition, and if that also fails, it runs the code in the else clause.

Iteration Control Structure

We use loops in Python for repetitive tasks. The two types of loops in Python are for and while.

  1. For Loop: A for loop in Python iterates over a sequence (like a list, tuple, string, or range) or other iterable objects.

Here’s a simple for loop:

for i in range(5):
    print(i)

This script prints numbers from 0 to 4.

  1. While Loop: A while loop in Python executes a block of code as long as a specified condition is true.

Here’s a simple while loop:

counter = 0
while counter < 5:
    print(counter)
    counter += 1

This script also prints numbers from 0 to 4.

Remember that loop control statements like break and continue can further influence the flow in loops. The break statement terminates the loop prematurely, while the continue statement skips the rest of the current iteration and goes to the next one.

Introduction to Functions in Python

Definition of Functions

In programming, a function is a reusable piece of code that performs a specific task. Functions help to organize the code, make it more readable, and allow a piece of code to be reused in different parts of a program, or even in different programs.

In Python, a function is defined using the def keyword, followed by a function name, a set of parentheses which may enclose some parameters, and a colon. A block of code indented under the function definition makes up the body of the function. Here’s an example of a simple Python function:

def greet():
    print("Hello, World!")

This function, when called, prints out the message “Hello, World!”.

Why Use Functions?

Functions are one of the fundamental building blocks in Python for several reasons:

  1. Code Reusability: Once a function is defined, it can be used anywhere in your program, reducing the need for writing the same code again and again.
  2. Modularity: Functions allow you to break down complex problems into smaller, manageable pieces of code. This makes the code easier to understand, test, and debug.
  3. Abstraction: When using a function, you don’t need to know the details of how the function works to use it. You just need to know what parameters it takes (if any) and what it returns. This is known as abstraction.
  4. Code Organization: Functions help you organize your code better. For example, you can group related functions in a Python module (a Python file), making your code easier to navigate.
  5. Maintainability: If you need to make a change to a piece of code that’s repeated throughout your program, you’d have to go and change it everywhere if you didn’t use functions. But if you’ve used a function, you just need to update the function’s code, and the changes will take effect wherever the function is used.

By using functions, you can make your Python programs more efficient, maintainable, and readable. Functions are essential for structured and modular programming.

Defining and Calling Functions in Python

The ‘def’ Keyword

In Python, a function is defined using the def keyword, followed by a unique function name, parentheses (), and a colon :. The statements that form the body of the function start at the next line, and must be indented. Here’s a simple example:

def greet():
    print("Hello, World!")

Naming Conventions for Functions

When naming your functions in Python, you should follow these conventions:

  1. Function names should be lowercase, with words separated by underscores as necessary to improve readability. This is known as snake_case. For example, calculate_average, get_user_input.
  2. Function names should be descriptive and indicate what the function does. For example, print_message is a better name than just message.
  3. Avoid using the names of existing Python keywords or functions for your functions to prevent conflicts and confusion.

Calling a Function

Once a function is defined, you can call (or invoke) it by using its name followed by parentheses (). If the function requires arguments (values passed into the function), you would include them in the parentheses. Here’s how to call the greet function we defined earlier:

greet()  # Prints: Hello, World!

Practice Exercise: Creating and Calling a Simple Function

Now, let’s practice what we’ve learned by creating and calling a simple function.

Exercise:

  1. Define a function named say_hello that takes one parameter named name.
  2. Inside the function, print out the message “Hello, [name]!” where [name] is replaced with the value of the name parameter.
  3. Call this function with your own name as the argument.

Solution:

def say_hello(name):
    print(f"Hello, {name}!")

say_hello("Alice")  # Prints: Hello, Alice!

In this exercise, you defined a function that takes one argument, and then you called this function with the argument “Alice”. When you run this code, it should print out “Hello, Alice!”. You can call this function with different names, and it will greet each name accordingly.

Python Function Parameters and Arguments

Understanding Parameters and Arguments

In the context of functions, parameters and arguments can be a bit confusing because they can mean different things depending on how they’re used:

  • Parameters are the names used when defining a function or a method, and they are placeholders for the values that it will take when called.
  • Arguments are the actual values that are passed in when the function is called.
def say_hello(name):  # 'name' is a parameter
    print(f"Hello, {name}!")

say_hello("Alice")  # 'Alice' is an argument

Positional Arguments

Positional arguments are arguments that need to be passed into the function in correct positional order.

def describe_pet(animal_type, pet_name):
    print(f"\nI have a {animal_type}.")
    print(f"My {animal_type}'s name is {pet_name}.")

describe_pet('hamster', 'harry')  # 'hamster' and 'harry' are positional arguments

Keyword Arguments

Keyword arguments are arguments passed to a function by explicitly naming each parameter and the corresponding value.

describe_pet(animal_type='hamster', pet_name='harry')  # 'animal_type' and 'pet_name' are keyword arguments

Default Arguments

A default argument is a parameter that assumes a default value if a value is not provided in the function call for that parameter.

def describe_pet(pet_name, animal_type='dog'):  # 'dog' is a default argument
    ...

Variable-length Arguments (args and *kwargs)

Sometimes, you might want to define a function that can take any number of arguments, this is achieved by using the special syntax *args and **kwargs in the function signature.

  • *args allows you to pass an arbitrary number of positional arguments.
  • **kwargs allows you to pass an arbitrary number of keyword arguments.
def my_function(*args, **kwargs):
    for arg in args:
        print(arg)

    for key, value in kwargs.items():
        print(f"{key} = {value}")

my_function('Hello', 'World', name='John', age=30)

Practice Exercise: Function with Different Types of Arguments

Let’s create a function that utilizes positional arguments, keyword arguments, and default arguments.

Exercise:

  1. Define a function named make_sandwich that takes in a positional argument bread_type, a keyword argument toasted=False, and an arbitrary number of ingredients as a variable-length argument.
  2. If toasted is True, print “Toasting the {bread_type} bread.”
  3. Then, print “Adding the following ingredients to your {bread_type} sandwich:” and list out all the ingredients.

Solution:

def make_sandwich(bread_type, *ingredients, toasted=False):
    if toasted:
        print(f"Toasting the {bread_type} bread.")

    print(f"Adding the following ingredients to your {bread_type} sandwich:")
    for ingredient in ingredients:
        print(ingredient)

make_sandwich('whole grain', 'ham', 'cheese', 'lettuce', toasted=True)

In this exercise, we utilized different types of arguments to make a versatile sandwich-making function.

Return Statement in Python

Understanding the Return Statement

In Python, the return statement is used to send a result back from a function and stop the execution of the function. If the return statement is without any expression, then the special value None is returned.

def add_numbers(a, b):
    return a + b  # The function returns the result of a + b

result = add_numbers(3, 5)  # The return value is stored in the variable 'result'
print(result)  # Prints: 8

Returning Single Values

A function in Python can return a single value. This value can be of any data type, including complex types like lists, dictionaries, or even other functions or classes.

def get_full_name(first_name, last_name):
    return first_name + " " + last_name  # The function returns a string

name = get_full_name("Alice", "Johnson")  # The return value is stored in the variable 'name'
print(name)  # Prints: Alice Johnson

Returning Multiple Values

A function in Python can return multiple values. These values are usually returned in the form of a tuple, which can then be destructured into multiple variables.

def get_info():
    name = "Alice"
    age = 25
    return name, age  # The function returns two values

person_name, person_age = get_info()  # The return values are destructured into two variables
print(person_name)  # Prints: Alice
print(person_age)  # Prints: 25

Practice Exercise: Functions with Return Statements

Let’s practice writing a function that returns both a single value and multiple values.

Exercise:

  1. Write a function called calculate_area that takes in the length and width of a rectangle as arguments, and returns the area.
  2. Write another function called calculate_dimensions that takes in the area and length of a rectangle, and returns both the width and the perimeter.

Solution:

def calculate_area(length, width):
    return length * width

def calculate_dimensions(area, length):
    width = area / length
    perimeter = 2 * (length + width)
    return width, perimeter

# Calculate the area of a rectangle with length = 5 and width = 4
area = calculate_area(5, 4)
print(area)  # Prints: 20

# Calculate the width and perimeter of a rectangle with area = 20 and length = 5
rect_width, rect_perimeter = calculate_dimensions(area, 5)
print(rect_width)  # Prints: 4.0
print(rect_perimeter)  # Prints: 18.0

In this exercise, we used the return statement to calculate and return the area, width, and perimeter of a rectangle.

Local and Global Variables in Python

Understanding Local Variables

A variable declared inside the function’s body or in the local scope is known as a local variable. These variables can only be accessed inside the function they are declared in.

def my_function():
    local_var = "I'm a local variable"
    print(local_var) 

my_function()  # Prints: I'm a local variable
print(local_var)  # Error: local_var is not defined

The variable local_var is a local variable. It’s available from the point it’s declared until the end of the function.

Understanding Global Variables

Global variables are the one that are declared outside of the function or in global scope. This means that a global variable can be accessed inside or outside of the function.

global_var = "I'm a global variable"

def my_function():
    print(global_var)

my_function()  # Prints: I'm a global variable
print(global_var)  # Prints: I'm a global variable

The variable global_var is a global variable. It’s available from the point it’s declared throughout the rest of your program.

Global Keyword

If you want to use a global variable inside a function, but there is a local variable with the same name, you can use the global keyword to indicate the variable is a global variable.

global_var = "I'm a global variable"

def my_function():
    global global_var
    global_var = "The global variable has been changed"
    print(global_var)

my_function()  # Prints: The global variable has been changed
print(global_var)  # Prints: The global variable has been changed

Practice Exercise: Differentiating Local and Global Variables

Let’s practice differentiating between local and global variables.

Exercise:

  1. Declare a global variable x and set it to 10.
  2. Define a function change_variables that declares a local variable x and sets it to 20, and changes the global x to 30.
  3. Call change_variables and then print x.

Solution:

x = 10  # This is a global variable

def change_variables():
    x = 20  # This is a local variable
    print("Local x inside function:", x)

    global x  # This refers to the global x
    x = 30

change_variables()
print("Global x outside function:", x)  # Prints: Global x outside function: 30

In this exercise, you learned the difference between local and global variables, and how to change a global variable from within a function.

Anonymous Functions: Lambda Functions in Python

Introduction to Lambda Functions

In Python, lambda functions are small anonymous functions. They can be used wherever function objects are required. They are syntactically restricted to a single expression. Like def, the lambda keyword creates a function to be called later, but it returns the function instead of assigning it to a name.

Writing Lambda Functions

Lambda functions are written as follows:

lambda arguments: expression

Here is an example of a simple lambda function:

double = lambda x: x * 2

This lambda function takes one argument x and returns x * 2. It’s equivalent to the following regular function:

def double(x):
    return x * 2

To call a lambda function, you use the same syntax as a regular function. For example, double(5) would return 10.

Lambda functions can take any number of arguments but can only have one expression. The expression is evaluated and returned, and lambda functions can be used wherever function objects are required.

Practice Exercise: Simple Lambda Functions

Let’s practice writing a lambda function.

Exercise:

  1. Write a lambda function power that takes two arguments x and y and returns x to the power of y.

Solution:

power = lambda x, y: x ** y

print(power(2, 3))  # Prints: 8

In this exercise, you wrote a lambda function that takes two arguments and used the power operator to calculate x to the power of y.

Python Built-in Functions

Introduction to Python Built-in Functions

Python comes with a set of built-in functions that are readily available for use. These functions provide a base level of functionality for the language and are very powerful tools for different types of tasks. They are universally available, which means you don’t need to import a module to use them.

Commonly Used Built-in Functions

Here are some commonly used built-in functions in Python:

  1. print(): This function prints the specified message to the screen.
print("Hello, World!")  # Prints: Hello, World!
  1. input(): This function allows user input.
name = input("Enter your name: ")  # Waits for the user to type something and press enter
  1. len(): This function returns the number of items (length) in an object.
print(len("Hello, World!"))  # Prints: 13
  1. type(): This function returns the type of an object.
print(type(123))  # Prints: <class 'int'>
  1. str(), int(), float(): These functions convert values to string, integer, or float data type, respectively.
print(str(123))  # Prints: '123'
print(int('123'))  # Prints: 123
print(float('123'))  # Prints: 123.0
  1. list(), dict(), tuple(), set(): These functions convert values to list, dictionary, tuple, or set data type, respectively.
print(list('123'))  # Prints: ['1', '2', '3']
print(dict([(1, 'a'), (2, 'b')]))  # Prints: {1: 'a', 2: 'b'}
print(tuple([1, 2, 3]))  # Prints: (1, 2, 3)
print(set([1, 2, 2, 3, 3, 3]))  # Prints: {1, 2, 3}

Practice Exercise: Exploring Built-in Functions

Let’s put into practice some of the built-in functions we’ve learned.

Exercise:

  1. Ask the user to input their name and age using the input() function.
  2. Print the length of their name using the len() function.
  3. Convert their age to an integer using the int() function, add one to it, and tell them how old they will be next year.

Solution:

name = input("Enter your name: ")
age = input("Enter your age: ")

print(f"The length of your name is {len(name)} characters.")

next_year_age = int(age) + 1
print(f"Next year you will be {next_year_age} years old.")

In this exercise, you got to practice with input(), len(), and int(), some of Python’s built-in functions.

Function Documentation: Docstrings

Importance of Function Documentation

Documenting your code is a fundamental practice in software development that leads to improved readability and understanding of your code. When it comes to functions in Python, documentation is often provided in the form of docstrings. Docstrings, short for documentation strings, are descriptive text written between triple quotes that explain what a function does.

Single Line and Multi-line Docstrings

In Python, docstrings can be single-line or multi-line.

  1. Single-line Docstrings: As the name suggests, these are brief descriptions that fit in one line. Single-line docstrings are used for simple functions. They are written like this:
def add(a, b):
    """Adds two numbers together."""
    return a + b
  1. Multi-line Docstrings: These are used for more complex functions that require a more detailed explanation. The first line provides a brief summary, followed by a more comprehensive description. It is also common to include other sections such as Arguments, Returns, and Raises.
def add(a, b):
    """
    Adds two numbers together.

    Arguments:
    a -- the first number
    b -- the second number

    Returns: The sum of a and b.
    """
    return a + b

Accessing Docstrings

Docstrings can be accessed using the .__doc__ attribute of the function. This can be useful to dynamically access the documentation of a function for reference. Here’s an example:

def add(a, b):
    """Adds two numbers together."""
    return a + b

print(add.__doc__)  # Prints: Adds two numbers together.

Also, docstrings are used by Python’s help function. You can get the docstring and some other useful info about a function like this:

help(add)

This practice of writing docstrings is very useful for understanding what a function does, especially when you’re working on large codebases or collaborating with other developers. It’s a good habit to write docstrings for your functions, no matter how simple they may seem.

Debugging Functions in Python

Common Errors in Function Definition and Usage

When defining and using functions in Python, you might come across several common errors. Here are a few examples:

  1. Syntax Error: This occurs when Python’s syntax rules are violated.
def add(a, b)  # Missing colon at the end
    return a + b
  1. Indentation Error: Python uses indentation to define blocks of code. Indentation errors occur when this rule is not followed.
def add(a, b):
return a + b  # Incorrect indentation
  1. Name Error: This occurs when a variable is used before it has been defined.
def add(a, b):
    return result  # result is not defined

print(add(5, 3))
  1. Type Error: This occurs when an operation is applied to a variable of an inappropriate type.
def add(a, b):
    return a + b

print(add(5, "3"))  # TypeError because you can't add an integer and a string

Tips to Debug Functions

When encountering an error, there are several steps you can follow to debug your functions:

  1. Read the Error Message: Python error messages can provide a lot of insight into what went wrong. The error message will typically include the type of error, the line number where the error occurred, and a traceback showing the sequence of function calls leading to the error.
  2. Print Variable Values: Use print() statements to display the values of variables at different points in your function. This can help you understand how your variables are changing and where things might be going wrong.
  3. Use a Debugger: Python comes with a built-in debugger called pdb. This tool allows you to step through your code one line at a time and inspect the values of variables at each step.
  4. Write Test Cases: Write tests for your function to make sure it behaves as expected in different scenarios. This can help you find edge cases that your function doesn’t handle correctly.
  5. Code Review: Have a fellow programmer review your code. Sometimes a fresh pair of eyes can spot something you might have missed.

Remember that debugging is a skill that improves with practice. The more you code and debug, the better you’ll become at it.

Sure, we will make the project simpler and ensure it only utilizes the material covered this week.

Practice Project

Brief: Develop a Small Python Program Utilizing Learned Function Concepts

For this project, we will create a simple program that performs basic mathematical operations. This will help reinforce your understanding of defining functions, using parameters and return statements.

Requirements:

  1. Write a function for each of the four basic mathematical operations: addition, subtraction, multiplication, and division.
  2. Each function should take two parameters (the numbers to operate on) and return the result of the operation.
  3. Write a fifth function that takes three parameters: the two numbers to operate on, and the operation to perform. This function should call one of the four previous functions based on the operation parameter and return the result.
  4. Test all the functions and print the results.

Here is a simple example solution:

# Define the four basic mathematical functions

def add(a, b):
    """Adds two numbers."""
    return a + b

def subtract(a, b):
    """Subtracts the second number from the first."""
    return a - b

def multiply(a, b):
    """Multiplies two numbers."""
    return a * b

def divide(a, b):
    """Divides the first number by the second. Returns None if division by zero."""
    return a / b if b != 0 else None

# Define a function that takes the two numbers and an operation to perform

def calculate(a, b, operation):
    """Performs the given operation on two numbers."""
    if operation == "add":
        return add(a, b)
    elif operation == "subtract":
        return subtract(a, b)
    elif operation == "multiply":
        return multiply(a, b)
    elif operation == "divide":
        return divide(a, b)
    else:
        return None

# Test the functions

print(add(5, 3))  # 8
print(subtract(5, 3))  # 2
print(multiply(5, 3))  # 15
print(divide(5, 3))  # 1.6666666666666667
print(calculate(5, 3, "add"))  # 8
print(calculate(5, 3, "subtract"))  # 2
print(calculate(5, 3, "multiply"))  # 15
print(calculate(5, 3, "divide"))  # 1.6666666666666667

This program demonstrates how to define functions, use parameters and return values, and call functions from other functions. As you continue learning Python, you’ll be able to create much more complex and interesting programs.

Summary and Next Steps

Recap of Functions in Python

Today, you’ve learned about the foundational aspect of Python programming: Functions. We discussed what functions are, why they are useful, and how to define and call them. We explored different types of function parameters, the return statement, local and global variables, and lambda (anonymous) functions. We also covered Python’s built-in functions, documenting functions using docstrings, and common tips for debugging functions. Finally, you got to practice these concepts by creating a simple math operation program.

Preview of Next Day’s Lesson: Introduction to Python’s Data Structures

In the next lesson, we will dive into an important aspect of Python: Data Structures. You’ll learn about the four built-in data structures in Python: Lists, Tuples, Dictionaries, and Sets. We’ll discuss how they are used, their characteristics, and their advantages and disadvantages. Understanding these data structures is essential to solve a wide range of problems when programming in Python.

Self-Study Resources and Reading Recommendations

  1. Python.org Official Documentation – Here you can find the official Python documentation on functions.
  2. Learn Python the Hard Way – This book by Zed Shaw is a great resource for beginners. It takes a hands-on approach to teaching Python and covers functions in detail.
  3. Automate the Boring Stuff with Python – This book by Al Sweigart is perfect for beginners and it’s a lot of fun. It teaches Python programming by guiding you through projects to automate tasks on your computer.
  4. Codecademy’s Python Course – This interactive course has a great module on functions.
  5. Real Python – This website has many great tutorials and articles about all aspects of Python programming, including functions.

Remember, the key to learning programming is practice. Try to spend some time each day coding, even if it’s only for a little bit. Happy coding!